Macro to check if running a spec

Doing some dependency injection and basically want a macro to insert some code if we’re running a spec.

class NeedsTesting
  class_property mock_manager : Manager? = nil
  property manager : Manager { @@mock_manager || Manager.instance }
end

I am using the class property to inject a mock and otherwise leave the class working as it is.
However I don’t want the overhead of checking for the mock manager in production

So thinking of doing something like

{% begin %}
property manager : Manager { {% if running_spec? %} @@mock_manager || {% end %} Manager.instance }
{% end %}

is there anything built-in that does this?

Best way I can think of to handle this is either use your own compile time flag, or just leverage constructor injection and don’t worry about it.

E.g. something like

module ManagerInterface
  # ...
end

class NeedsTesting
  @manager : ManagerInterface

  def initialize(@manager : ManagerInterface); end
end

# In production
obj = NeedsTesting.new real_manager_you_get_from_somewhere_or_new_up

# In `spec_helper`
class MockManager
  include ManagerInterface

  # Define additional methods for setting up the obj for each test case.
  # ...
end

obj = NeedsTesting.new MockManager.new

I suppose you could try @type.has_constant?("Spec") on the top-level scope. That tells if the Spec module was included (it must be included before this macro expands, though).

Not sure if this is really a good idea, though. I’d probably favor a more direct approach.
You already need to setup the class property somewhere in your spec code. Perhaps it would be easier to apply the default value there as well, for example through reopening the class?

3 Likes

re-opening the class is what I went with.
so now production code is:

property manager : Manager { Manager.instance }

tests re-open the class and update it to

class_property mock_manager : Manager? = nil
property manager : Manager { @@mock_manager || Manager.instance }

Isn’t it a bit of a code smell to have the class itself have a spec/test only property? Or I assume you’re also monkey patching in the mock_manager class var?

2 Likes

updated my comment above with the spec code

2 Likes

It might be, in other languages. For example in Ruby there’s TimeCop where you can change what Time.now returns. That’s impossible to do in other languages, but it’s fine and even preferred in Ruby. That way you don’t have to pass a Clock interface everywhere.

PHP’s Carbon has Carbon - A simple PHP API extension for DateTime.. Part of me has wondered if that would make for a good API for the Time type in the stdlib.