开发者

Mockito verify state halfway through test

I have some code that put simply, sets an object to a state of PROCESSING, does some stuff, then sets it to SUCCESS. I want to verify that the PROCESSING save is done with the correct values.

The problem is when the verify() tests are performed, .equals() is called on the object as it is at the end of the test, rather than halfway through.

For example the code:

public void process(Thing thing) {
    thing.setValue(17);
    thing.setStatus(Status.PROC开发者_JAVA技巧ESSING);
    dao.save(thing);

    doSomeMajorProcessing(thing);

    thing.setStatus(Status.SUCCESS);
    dao.save(thing);
}

I want to test:

public void test() {
    Thing actual = new Thing();
    processor.process(actual);

    Thing expected = new Thing();
    expected.setValue(17);
    expected.setStatus(Status.PROCESSING);
    verify(dao).save(expected);

    // ....

    expected.setStatus(Status.SUCCESS);
    verify(dao).save(expected);
}

On the first verify, actual.getStatus() is Status.SUCCESS, as Mockito just keeps a reference to the object and can only test it's value at the end.

I have considered that if a when(...) where involved then .equals() would be called at the correct time and the result would only happen if Thing was what I wanted it to be. However, in this case .save() returns nothing.

How can I verify that the object is put into the correct states?


Ok, I found a solution, but it's pretty horrible. Verify is no good to me because it runs too late, and stubbing is hard because the method returns a void. But what I can do is stub and throw an exception if anything but the expected is called, while validating that something is called:

public void test() {
    Thing actual = new Thing();

    Thing expected = new Thing();
    expected.setValue(17);
    expected.setStatus(Status.PROCESSING);

    doThrow(new RuntimeException("save called with wrong object"))
            .when(dao).saveOne(not(expected));

    processor.process(actual);

    verify(dao).saveOne(any(Thing.class));

    // ....

    expected.setStatus(Status.SUCCESS);
    verify(dao).saveTwo(expected);
}

private <T> T not(final T p) {
    return argThat(new ArgumentMatcher<T>() {
        @Override
        public boolean matches(Object arg) {
            return !arg.equals(p);
        }
    });
}

This infers that expected is called. Only drawback is that it'll be difficult to verify the method twice, but luckily in my case both DAO calls are to different methods, so I can verify them separately.


Why not just mock the Thing itself and verify that? eg:

public class ProcessorTest {

    @Mock
    private Dao mockDao;
    @InjectMocks
    private Processor processor;

    @BeforeMethod   
    public void beforeMethod() {
        initMocks(this);
    }

    public void test() {

        Thing mockThing = Mockito.mock(Thing.class);
        processor.process(thing);

        verify(mockThing).setStatus(Status.PROCESSING);
        verify(mockThing).setValue(17);
        verify(mockDao).save(mockThing);
        verify(mockThing).setStatus(Status.SUCCESS);
    }

If you want to explicitly test the order in which these things happen, use an InOrder object:

public void inOrderTest() {

    Thing mockThing = Mockito.mock(Thing.class);
    InOrder inOrder = Mockito.inOrder(mockThing, mockDao);

    processor.process(mockThing);

    inorder.verify(mockThing).setStatus(Status.PROCESSING);
    inorder.verify(mockThing).setValue(17);
    inorder.verify(mockDao).save(mockThing);
    inorder.verify(mockThing).setStatus(Status.SUCCESS);
    inorder.verify(mockDao).save(mockThing);
}


Mockito has a problem verifying mutable objects. There is an open issue about this (http://code.google.com/p/mockito/issues/detail?id=126)

Maybe you should switch to EasyMock. They use a record/playback pattern and do the verification at the time of the call in contrary to Mockito, where the verification happens after the call.

This Mockito version of the test has the mentioned problem:

@Test
public void testMockito() {
    Processor processor = new Processor();
    Dao dao = Mockito.mock(Dao.class);
    processor.setDao(dao);

    Thing actual = new Thing();
    actual.setValue(17);
    processor.process(actual);

    Thing expected1 = new Thing();
    expected1.setValue(17);
    expected1.setStatus(Status.PROCESSING);
    verify(dao).save(expected1);

    Thing expected2 = new Thing();
    expected2.setValue(19);
    expected2.setStatus(Status.SUCCESS);
    verify(dao).save(expected2);
}

This EasyMock version works fine:

@Test
public void testEasymock() {
    Processor processor = new Processor();
    Dao dao = EasyMock.createStrictMock(Dao.class);
    processor.setDao(dao);

    Thing expected1 = new Thing();
    expected1.setValue(17);
    expected1.setStatus(Status.PROCESSING);
    dao.save(expected1);

    Thing expected2 = new Thing();
    expected2.setValue(19);
    expected2.setStatus(Status.SUCCESS);
    dao.save(expected2);

    EasyMock.replay(dao);

    Thing actual = new Thing();
    actual.setValue(17);
    processor.process(actual);

    EasyMock.verify(dao);
}

In my example doSomeMajorProcessing sets value to 19.

private void doSomeMajorProcessing(Thing thing) {
    thing.setValue(19);     
}


After reviewing https://code.google.com/archive/p/mockito/issues/126 I was able to get my version of this working (Java 15, Mockito 3.6.28):

// ========= CODE ==========
public void process(Thing thing) {
    thing.setValue(17);
    thing.setStatus(Status.PROCESSING);
    dao.save(thing);

    doSomeMajorProcessing(thing);

    thing.setStatus(Status.SUCCESS);
    dao.save(thing);
}


// ========= TEST ==========

// ++++++ NOTE - put this at class level 

private final Dao dao = mock(Dao.class, withSettings().defaultAnswer(new ClonesArguments()));

public void test() {
    Thing actual = new Thing();
    processor.process(actual);

    ArgumentCaptor<Thing> captor = ArgumentCaptor.for(Thing.class);
    verify(dao, times(2)).save(captor.capture());
    List<Things> savedCalls = captor.getAllValues();
    assertEquals(Status.PROCESSING, savedCalls.get(0).getStatus());
    assertEquals(Status.SUCCESS, savedCalls.get(1).getStatus());
}


Using argThat with a hamcrest Matcher should do the trick. The Matcher would match its passed thing if the thing has the PROCESSING status:

public class ProcessingMatcher extends BaseMatcher<Thing> {
    @Override
    public boolean matches(Object item) {
        if (item instanceof Thing) {
            return ((Thing) item).getStatus() == Status.PROCESSING;
        }
        return false;
    }

    @Override
    public void describeTo(Description description) {
        description.appendText(" has the PROCESSING status");
    }
}

And then in your test, use the following code :

public class MyTest {

    public void test() {
        //...
        mockDao.save(argThat(hasTheProcessingStatus()));
    }

    static ProcessingMatcher hasTheProcessingStatus() {
        return new ProcessingMatcher();
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜