First, go check out Jon's code, then come back here.
The problem lies in the following test. I can comment out the part that looks like it is satisfying the assertions, yet the test still passes—a false positive.
[Test]
public void Another_way_to_verify_expectations_instead_of_AssertWasCalled()
{
var stub = MockRepository.GenerateStub<ISampleClass>();
// Here I'm setting up an expectation that a method will be called
stub.Expect(s => s.MethodThatReturnsInteger("foo")).Return(5);
//Sneaky Sharon comments out the "Act" part of the test:
//var output = stub.MethodThatReturnsInteger("foo");
//Assert.AreEqual(5, output);
// ... and now I'm verifying that the method was called
stub.VerifyAllExpectations();
}
In translation, that test says: Create a fake ISampleClass; set up an expectation that a method will be called;
There are two things to fix here. The first is that this test is a little solipsistic. If you create a mock, tell the mock to act, and verify things about the mock... all you're testing are mocks. Instead, you want your tests to exercise real code. The "system under test," i.e., the class being tested, should be part of your production code. Its dependencies are what get mocked, so that you can verify proper interactions with those dependencies. Let's fix the solipsism before going on to the second issue.
As originally written, the expectation would be satisfied by the "Act" (as in Arrange-Act-Assert) part of the test. It says, "Call this method. Did I just call this method? Oh, good." Instead, you want to ensure the system under test correctly interacts with its friends, using Rhino Mocks' AssertWasCalled and Expect methods. We need a real class that takes the stubbed class and calls a method on the stubbed class, and we'll write unit tests around the real class.
public class MyRealClass
{
public void ActOnTheSampleClass(ISampleClass sampleClass)
{
}
}
Here's the re-written test, verifying how my real class interacts with the ISampleClass interface.
[Test]
public void Another_way_to_verify_expectations_instead_of_AssertWasCalled()
{
var stub = MockRepository.GenerateStub<ISampleClass>();
var systemUnderTest = new MyRealClass();
// Here I'm setting up an expectation that a method will be called
stub.Expect(s => s.MethodThatReturnsInteger("foo")).Return(5);
// Tell the system to act (which, if it is working correctly,
// will call a method on the ISampleClass.
systemUnderTest.ActOnTheSampleClass(stub);
// ... and now I'm verifying that the method was called
stub.VerifyAllExpectations();
}
This test will still pass, despite the fact that my real class does not currently call any methods on the ISampleClass interface. This points to the second issue to fix. In Rhino Mocks, expectations on stubs are not verified; only mocks are verified. If an object is created with GenerateStub instead of GenerateMock, then its VerifyAllExpectations method doesn't do anything. This is non-obvious because the AssertWasCalled and AssertWasNotCalled methods on a stub will behave the way you want them to.
In Rhino Mocks, a stub can keep track of its interactions and assert that they happened, but it cannot record expectations and verify they were met. A mock can do both these things.
That is how they are implemented in Rhino Mocks. If you were holding firm to the ideas in Fowler's Mocks Aren't Stubs article, I think stubs would implement neither VerifyAll nor AssertWasCalled. Semantically, verifying expectations and asserting interactions are synonymous, if you ask me; therefore, stubs shouldn't do either one.
Back to Jon Kruger's tests. If we call GenerateMock instead of GenerateStub, the test will fail properly with an ExpectationViolationException.
[Test]
public void Another_way_to_verify_expectations_instead_of_AssertWasCalled()
{
var stub = MockRepository.GenerateMock<ISampleClass>();
var systemUnderTest = new MyRealClass();
// Here I'm setting up an expectation that a method will be called
stub.Expect(s => s.MethodThatReturnsInteger("foo")).Return(5);
// Tell the system to act (which, if it is working correctly,
// will call a method on the ISampleClass.
systemUnderTest.ActOnTheSampleClass(stub);
// ... and now I'm verifying that the method was called
stub.VerifyAllExpectations();
}
Now that we're red, let's get to green. Change the system under test so that it does its job as expected.
public class MyRealClass
{
public void ActOnTheSampleClass(ISampleClass sampleClass)
{
sampleClass.MethodThatReturnsInteger("foo");
}
}
Wahoo, a passing test that we can rely on.
The two key points from this exercise are:
- Describing Rhino Mocks with unit tests is a cool way to explain a topic. Let's have more executable documentation, eh?
- Expectations on stubs aren't verified, so beware of falsely passing tests.
1 comment:
Good to see Sharon being Sharon. All is right with the world. :)
Dave
Post a Comment