Rails: How do I write tests for a ruby module?

I would like to know how to write unit tests for a module that is mixed into a couple of classes but don't quite know how to go about it:

  1. Do I test the instance methods by writing tests in one of the test files for a class that includes them (doesn't seem right) or can you somehow keep the tests for the included methods in a separate file specific to the module?

  2. The same question applies to the class methods.

  3. Should I have a separate test file for each of the classes in the module like normal rails models do, or do they live in the general module test file, if that exists?


Solution 1:

IMHO, you should be doing functional test coverage that will cover all uses of the module, and then test it in isolation in a unit test:

setup do
  @object = Object.new
  @object.extend(Greeter)
end

should "greet person" do
  @object.stubs(:format).returns("Hello {{NAME}}")
  assert_equal "Hello World", @object.greet("World")
end

should "greet person in pirate" do
  @object.stubs(:format).returns("Avast {{NAME}} lad!")
  assert_equal "Avast Jim lad!", @object.greet("Jim")
end

If your unit tests are good, you should be able to just smoke test the functionality in the modules it is mixed into.

Or…

Write a test helper, that asserts the correct behaviour, then use that against each class it's mixed in. Usage would be as follows:

setup do
  @object = FooClass.new
end

should_act_as_greeter

If your unit tests are good, this can be a simple smoke test of the expected behavior, checking the right delegates are called etc.

Solution 2:

Use inline classes (I am not doing any fancy flexmock or stubba/mocha usage just to show the point)

def test_should_callout_to_foo
   m = Class.new do
     include ModuleUnderTest
     def foo
        3
     end
   end.new
   assert_equal 6, m.foo_multiplied_by_two
 end

Any mocking/stubbing library out there should give you a cleaner way to do this. Also you can use structs:

 instance = Struct.new(:foo).new
 class<<instance
     include ModuleUnderTest
 end
 instance.foo = 4

If I have a module that is being used in many places I have a unit test for it which does just that (slide a test object under the module methods and test if the module methods function properly on that object).