Search code examples
ruby-on-railsunit-testingrspecintegration-testingcommand-pattern

Should I test the command pattern with pure unit tests or integration tests?


The command pattern is a pretty good way of avoiding fat controllers and fat models where several components have to work together. This is the place where business logic is implemented. The question is that I'm not sure if 'pure' unit testing has sense here or it is better to write integration tests instead. As an example:

class MyCommand
  def initialize(attributes)
    @attributes = attributes
  end

  def execute
    objA = ClassA.find(@attributes[:class_a_id])
    objB = ClassB.find(@attributes[:class_b_id])

    objA.do_something
    objB.do_something_with_a(objA)

    objA.save
    objB.save
  end
end

Note that this is an example. Of course we could write a much better command, but this one is fine as an example.

As we can see, objA and objB are instantiated inside the command. I could use DI to avoid this internal dependency, but if I do this, the instantiation would be done inside the controller which uses this command and this is exactly what I don't want.

If I write an unit test for this, I would have to stub ClassA.find and ClassB.find methods and check that do_something and do_something_with_a is called. Also we have to check that save is called as well. Too much stubbing over here and a very brittle test.

We could solve a piece of this by using FactoryGirl

...
before do
  FactoryGirl.create(:class_a)
  FactoryGirl.create(:class_b)
end
...

And then, we don't need to stub ActiveRecord find methods.

To avoid expected method invocation on save, we could check that the database has been updated properly.

If we chose this second approach, we are writing an integration test that involves collaboration among ClassA, ClassB, MyCommand and the database. Actually this is like a functional test because we are testing a piece of the functional logic (just like we do with controller but framework independent).

So, the question is, is it worth it to write pure unit tests for commands?

Thanks in advance


Solution

  • The answer depends on how you're going to design real commands.

    In your example the Command delegates most of its work to ClassA and ClassB. If every Command just looks up some objects, calls model methods on them that do most of the work and saves them, you should write pure unit tests -- you wouldn't want to duplicate the testing of the model methods in the tests of the Command. It won't be hard to write pure unit tests since there aren't many method calls. You may be able to abstract the load/invoke/save pattern into a Command superclass and test it only once (or in methods that you share among your tests of Commands). Even better, if you already have acceptance tests, you may not need to test Commands like that at all -- they may already be completely covered by the acceptance tests.

    But if your Commands just delegate to your models, you won't be slimming down your models. All of the real code will still be in your models. If a Command calls a method that is only used in that Command, move the method to the Command. If doing that would give the method too much Feature Envy, extract a module included by the model whose purpose is just to support the Command.

    Either way, you'll move a lot of your model code into Commands and maybe Command support modules. That code is a lot like model code, and in my experience the cleanest way to test it is the same way you test model code, by writing tests (technically integration tests) which load database objects and assert what the code does to them, rather than mocking.

    In general I've found that when a Rails app starts to grow a layer between models and controllers (I like facades or business process controllers, myself), testing that layer with database objects as I would the model layer works well, whereas testing it with mocks like I'd test the controller layer would be, as you said, painful and brittle.