views:

78

answers:

2

This is a test suite that is green using Rhino Mocks.

[SetUp]
  public void BeforeEachTest()
  {
     _mocksRepo = new MockRepository();
     _mockBank = _mocksRepo.StrictMock<IBank>();
     //_mockPrinter = _mocksRepo.StrictMock<IPrinter>();
     _mockPrinter = _mocksRepo.DynamicMock<IPrinter>();
     _mockLogger = _mocksRepo.StrictMock<ILog>();

     _testSubject = new CrashTestDummy(DUMMY_NAME, _mockPrinter, _mockLogger);
  }

  [TearDown]
  public void AfterEachTest()
  {
     _mocksRepo.ReplayAll(); // 2nd call to ReplayAll does nothing. Safeguard check
     _mocksRepo.VerifyAll();
  }

  [Test]
  public void Test_ConstrainingArguments()
  {
     _mockPrinter.Print(null);
     LastCall.Constraints(Text.StartsWith("The current date is : "));
     _mocksRepo.ReplayAll();

     _testSubject.PrintDate();
  }

Now to make a test green in another fixture, I had to make a slight change to the ctor - subscribe to an event in the printer interface. This resulted in all tests in the above test fixture going red.

public CrashTestDummy(string name, IPrinter namePrinter, ILog logger)
      {
         _printer = namePrinter;
         _name = name;
         _logger = logger;

         _printer.Torpedoed += KaboomLogger;   // CHANGE
      }

The NUnit errors tab shows

LearnRhinoMocks.Rhino101.Test_ConstrainingArguments:
TearDown : System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.
  ----> Rhino.Mocks.Exceptions.ExpectationViolationException : IPrinter.add_Torpedoed(System.EventHandler`1[LearnRhinoMocks.MyEventArgs]); Expected #1, Actual #0.

The way to fix this is to move the line where the test subject is created from Setup() below the ReplayAll() line in the test. Rhino mocks thinks that you have setup an event subscribe as an expectation otherwise. However this fix means (some) duplication in each test. Each test usually adds some expectations before calling ReplayAll.

I know that this is a specific scenario which involves event subscription in the test subject ctor.

  • However this is a normal scenario e.g. in a ModelViewPresenter pattern, I'm curious to know if there is a recommended way to do this?
  • Also I didnt like the way multiple tests in a test fixture failed due to change driven by an external test ? Am I in test-design smell country?
+1  A: 

According to xUnit Test Patterns, you are, indeed, in test-design smell country :)

The issue is a test smell called General Fixture, which means that the run-time environment is always configured in the same way across many different tests.

It's important to realize that when it comes to xUnit Test Patterns, the term Fixture means something different than in NUnit. It's not a test class, but rather covers the concept of everything that must be in place as preconditions in a test case before you exercise the System Under Test (SUT).

It is very common to use a setup method such as your BeforeEachTest method to set up the Fixture, but there are other ways, which I'll get back to shortly.

The problem with General Fixture is that you attempt to cover too many specific test cases with slightly different preconditions with the same Fixture. This is one of the underlying reasons you now observe this interdependency between tests.

To compound the issue, NUnit is special in that it reuses the same instance of a particular test class across multiple test cases, so that state may stick from one test to another. All other xUnit frameworks create a new instance of the test class for each test case, so that this type of issue is less common.

This brings me back to an alternative to setting up the Fixture in a setup method (what is called Implicit Setup): write a method or object that encapsulates the Fixture and create it as the first line of code in each test case. This will also allow you to vary the Fixture slightly from test case to test case by parameterizing this Fixture setup code.

Here's an example of how I usually do this.

Mark Seemann
Sorry confused myself.. staring at this for too long... Yes. The resolution for 'inappropriately shared fixture' anti-patternis to write another xUnit testFixture class and duplicate all the member variables all tests fail. Option: move the creation of the test subject into each test after ReplayAll()
Gishu
contd.. which seems to be duplication to me.
Gishu
I concede that moving the line that creates the SUT into each test could be interpreted as duplication, but if you implement that actual creation of the SUT in a factory method, you wouldn't be repeating yourself in any way that matters.
Mark Seemann
+1  A: 

I do agree with Mark on using explicit setup instead of implicit setup. But on the use of RhinoMocks, have you tried something below. That you you can put a mock in replay mode temporary and resume recording later. Something like:


    SetupResult.For(_mockPrinter...);
    _mocksRepo.Replay(_mockPrinter);
    _testSubject = new CrashTestDummy(DUMMY_NAME, _mockPrinter, _mockLogger);
    _mocksRepo.BackToRecord(_mockPrinter, BackToRecordOptions.None);
Kenneth Xu
Works like a charm. Spent 30 mins because I was away from this page and forgot to use the None enumerated param. Good thing that Rhino moved to v3.5 :) Thanks a ton Kenneth.
Gishu