dimanche 28 février 2016

A tale of two mocks...same object type, but different subsequent actions - how to associate correct object with correct expectations?

When unit testing with mock objects and using PowerMock or PowerMokito to intercept instantiation of objects within the class under test, how does one handle the case where multiple instantiations of the same object occur, but each created instance must be a different mock object because they end up doing different things?

For example consider this class under test, which instantiates two objects of the same type. The constructor has no arguments, so I can't use argument matchers to distinguish the two objects when they are created:

public class classUnderTest {

    someTypeOfObject A;
    someTypeOfObject B;

    public void someMethod() {
        Object A = new someTypeOfObject();
        Object B = new someTypeOfObject();

        //Object A does something
        A.doSomething();

        //Object B does something *different*
        B.doSoemthingDifferent();

    }
}

I can then naively write a test for this, which returns a mock for Object A in the first instantiation, then a mock for Object B in the second.

public class testClass extends EasyMockSupport {
    public void testTheClassUnderTest() throws Exception {
        /* Create mocks for Object A and b */
        mockOfObjectA = mock(someTypeOfObject);
        mockOfObjectB = mock(someTypeOfObject);

        /* <----- Setup Mock expectations -------> */
        // Intercept Instantiation of object A, and return mock
        PowerMock.expectNew(someTypeOfObject.class)
                .andReturn(mockOfObjectA)
                .once();
        // Intercept Instantiation of object B, and return mock
        PowerMock.expectNew(someTypeOfObject.class)
                .andReturn(mockOfObjectB)
                .once();

        //<--Object A does something, returns itself for call chaining
        PowerMock.expect(mockOfObjectA.doSomething)
                .andReturn(mockOfObjectA);

        //<--Object B does something, returns itself for call chaining
        PowerMock.expect(mockOfObjectB.doSomethingDifferent)
                .andReturn(mockOfObjectB);
        /* <----- Done setting up mock expectations ------------> */

        //...more code to exercise object under test and verify expecations
    }
}

HOWEVER, there is nothing about the code that implies that Object A must be created before Object B. Object A and B are identical when they are created, and the arguments to the constructor are the same, so I don't see a way to tell PowerMock how to distinguish them so it can return the mocks for Object A or B when appropriate. So while the above test will work with the above code, the test will break if the code is ever refactored like this:

public class classUnderTest {

    someTypeOfObject A;
    someTypeOfObject B;

    public void someMethod() {
        Object B = new someTypeOfObject(); //<--Object B created first
        Object A = new someTypeOfObject();


        //Object A does something
        A.doSomething();

        //Object B does something *different*
        B.doSoemthingDifferent();

    }
}

How can I tell PowerMock how to distinguish object A from Object B during instantiation? Or is there some way to swap the expectations recorded between two objects so that I can handle the two cases (i.e. if verifyAll() fails, swap expectations between mockOfObjectA and mockOfObjectB and check again before failing the test).

Note that I don't own the class being instantiated, so answers involving modifications to the class under test cannot be used. Also, I'd really prefer to have an answer which doesn't drive anything about the implementation of the code under test.

Aucun commentaire:

Enregistrer un commentaire