views:

399

answers:

4

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?

+1  A: 

I would generally test the module in as much isolation as possible, essentially testing the methods, with just enough code, mocks and stubs to get it working.

I would then probably also have tests for the classes the modules is included in. I may not test every class, but would test enough of the classes to get good coverage and have insight into any issues that arise. These tests don't need to explicitly test the module, but would certainly test it's usage in particular scenarios.

Each set of tests would have its own file.

Toby Hede
Thanks, I agree with what you your saying about testing the functionality in the classes it's included in. So would you have a test file for each additional class in the module, or a test file for the module as a whole? I guess I'm more hung up on the actual test files (filenames, locations etc) as opposed to what to test.
tsdbrown
+4  A: 

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.

cwninja
When you say "functional test coverage" I guess your referring to the functionality that the models acquire and not controller tests stored test/functional? Thanks for your answer I like the idea of testing the module in isolation and writing a helper the other classes can call that use that module.
tsdbrown
By functional I mean from the outside in. This is usually a controller test, but not always.Either way, functional coverage should touch (or at least graze) all areas of the system. If your unit tests are strong, then functional testing is often enough to cover your ass.<rant>Writing too many low level tests can be a bad investment. If it is never going to fail alone, then does it catch bugs? Is the "probable debug time saved" * "probability of a bug" > "time to write the test"?Ignore this if a bug could kill people or your business.</rant>
cwninja
+1  A: 

I try to keep my tests focused only on the contract for that particular class/module. If I've proven the module's behavior in a test class for that module (usually by including that module in a test class declared in the spec for that module) then I won't duplicate that test for a production class that uses that module. But if there's additional behavior that I want to test for the production class, or integration concerns, I'll write tests for the production class. For instance I have a module called AttributeValidator that performs lightweight validations kind of similar to ActiveRecord. I write tests for the module's behavior in the module spec:

before(:each) do
    @attribute_validator = TestAttributeValidator.new
end

describe "after set callbacks" do
    it "should be invoked when an attribute is set" do
      def @attribute_validator.after_set_attribute_one; end
      @attribute_validator.should_receive(:after_set_attribute_one).once
      @attribute_validator.attribute_one = "asdf"
    end
end

class TestAttributeValidator 
    include AttributeValidator
    validating_str_accessor [:attribute_one, /\d{2,5}/]      
end

Now in a production class that includes the module, I won't re-assert that the callbacks are made, but I may assert that the included class has a certain validation set with a certain regular expression, something particular to that class, but not reproducing the tests I wrote for the module. In the spec for the production class, I want to guarantee that particular validations are set, but not that validations work in general. This is a kind of integration test, but one that doesn't repeat the same assertions I made for the module:

describe "ProductionClass validation" do
    it "should return true if the attribute is valid" do
        @production_class.attribute = @valid_attribute
        @production_class.is_valid?.should be_true
    end
    it "should return false if the attribute is invalid" do
        @production_class.attribute = @invalid_attribute
        @production_class.is valid?.should be_false
    end
end

There is some duplication here (as most integration tests will have), but the tests prove two different things to me. One set of tests prove the general behavior of the module, the other proves particular implementation concerns of a production class that uses that module. From these tests I know that the module will validate attributes and perform callbacks, and I know that my production class has a specific set of validations for specific criteria unique to the production class.

Hope that helps.

Dave Sims
+1  A: 

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).

Julik