views:

90

answers:

5

This is a simplified version of a class I'm writing a unit test for

class SomeClass {

    void methodA() {
        methodB();
        methodC();
        methodD();
    }

    void methodB() {
        //does something
    }

    void methodC() {
        //does something
    }

    void methodD() {
        //does something
    }
}

While writing the unit tests for this class, I've mocked out objects using EasyMock used in each method. It was easy to set up the mock objects and their expectation In method B,C,and D. But to test method A, I have to set up A LOT more mock objects and their expectations. Also, I’m testing method A in different conditions, meaning I have to setup the mock objects many times with different expectations.

In the end, my unit test becomes hard to maintain and pretty cluttered. I was wondering if anyone has or seen a good solution to this problem.

A: 

You could extract common setup code into separate (possibly parametrized) methods, then call them whenever appropriate. If the tests for methodA have a very different fixture from the tests of the other methods, there may not be much to put into the @Before method itself, so you need to call the appropriate combination of setup helper methods from the test methods themselves. It is still a bit cumbersome, but better than duplicating code all over the place.

Depending on what unit test framework you use, there may be other options too, but the above should work with any framework.

Péter Török
A: 

This is an example of a Fragile test because the mock setups have too intimate knowledge of the SUT.

I don't know EasyMock, but with Moq you don't need to setup void methods. However, with Moq the methods would have to be public or protected and virtual.

Mark Seemann
+2  A: 

If I understand your question correctly, I think that this is a matter of design. The nice thing about unit testing is that writing tests often forces you to make your design better. If you need to mock too many things while testing a method it often means you should split your class into two smaller classes, which will be easier to test (and write, and maintain, and bugfix, and reuse, etc.).

In your case, the method A seems to be at a higher level than methods A, B, C. You can consider removing it to a higher level class, that would wrap SomeClass:

class HigherLevelClass {
    ISomeClass someClass;

    public HigherLevelClass(ISomeClass someClass)
    {
        this.someClass = someClass;
    }

    void methodA() {
        someClass.methodB();
        someClass.methodC();
        someClass.methodD();
    }
}

class SomeClass : ISomeClass {
    void methodB() {
        //does something
    }

    void methodC() {
        //does something
    }

    void methodD() {
        //does something
    }
}

Now when you are testing methodA all you need to mock is the small ISomeClass interface and the three method calls.

Grzenio
+1 good answer.
al nik
A: 

For each test you're writing, consider the behaviour which is valuable for that test. You'll have some contexts you're setting up which the behaviour relies on, and some outcomes as a result of the behaviour that you want to verify.

Set up relevant contexts, verify the outcomes, and use NiceMocks for everything else.

I prefer Mockito (Java) or Moq (.NET) which work this way by default. Here's Mockito's page on Mockito vs. EasyMock so you can get the idea (EasyMock didn't have NiceMock before Mockito came along):

http://code.google.com/p/mockito/wiki/MockitoVSEasyMock

You can probably use EasyMock's NiceMock in a similar way. Hopefully this will help you detangle your tests. You can always import both frameworks and use them alongside each other / incrementally switch over if it helps.

Good luck!

Lunivore
A: 

I’m testing method A in different conditions, meaning I have to setup the mock objects many times with different expectations.

If you care of what methodA is doing and which collaborator function has to be called then you have to setup different expectations... I don't see how you can skip this step?!

If you testLogout you would expect a call to myCollaborator.logout() otherwise if you testLogin you would expect something like myCollaborator.login().

If you have many methods with lots/different expectations maybe is the case to split your class in collaborators

al nik