Elad Benda
Elad Benda

Reputation: 36656

how to use mockito spy to redirect a method call?

I have a method that relies on "now" Date object.

I want to write a unit-test for it.

So I want to inject a fake-fixed "now" date (making the test determine).

I have tried to inject a spy like this:

private ImagesSorter setServerWithSpyImageSorter(User user, List imagesAsInsertionOrder, Date fakeNowDate) throws IOException {
        ImagesSorter imagesSorter = spy(new ImagesSorter());
        when(imagesSorter.sortImages(imagesAsInsertionOrder, user)).thenReturn(imagesSorter.sortImages(imagesAsInsertionOrder, user, fakeNowDate));
        //doReturn(imagesSorter.sortImages(imagesAsInsertionOrder, user, fakeNowDate)).when(imagesSorter).sortImages(imagesAsInsertionOrder, user);
        server = VenueServerImplBuilder.create().withImagesSorter(imagesSorter).build();
        server.init();
        return imagesSorter;
    }

but it doesn't work.

1) when I used doReturn(imagesSorter.sortIm.. it was eagerly evaluated. I didn't want that to happen. Can I avoid this?

2) when I commented out the doReturn(.. and used when(imagesSorter.sor

I got the following error:

org.mockito.exceptions.misusing.UnfinishedStubbingException: 
Unfinished stubbing detected here:



E.g. thenReturn() may be missing.
Examples of correct stubbing:
    when(mock.isOk()).thenReturn(true);
    when(mock.isOk()).thenThrow(exception);
    doThrow(exception).when(mock).someVoidMethod();
Hints:
 1. missing thenReturn()
 2. you are trying to stub a final method, you naughty developer!

how would you code what I want to do?

Upvotes: 1

Views: 3692

Answers (2)

luboskrnac
luboskrnac

Reputation: 24561

This syntax doesn't work for spies:

when(imagesSorter.sortImages(imagesAsInsertionOrder, user)).thenReturn(imagesSorter.sortImages(imagesAsInsertionOrder, user, fakeNowDate));

You need to use this construct:

doReturn(imagesSorter.sortImages(imagesAsInsertionOrder, user, fakeNowDate)).when(imagesSorter).sortImages(imagesAsInsertionOrder, user));

Here is relevant documentation (see section "Important gotcha on spying real objects!"): http://static.javadoc.io/org.mockito/mockito-core/2.8.47/org/mockito/Mockito.html#13

Upvotes: 1

Benjamin
Benjamin

Reputation: 1846

I don't think you need Mockito to create your mock here. Since ImageSorter is a concrete class, you can't make a real decorator, but you can make something like:

public class FixedDateImageSorter extends ImagesSorter {

    final Date fixedDdate;

    FixedDateImageSorter(Date fixedDate) {
        this.fixedDdate = fixedDate;
    }

    public List sortImages(List s, User u) {
        return sortImages(s, u, fixedDdate);
    }
}

Then

private ImagesSorter setServerWithSpyImageSorter(User user, List imagesAsInsertionOrder, Date fakeNowDate) throws IOException {
    ImagesSorter imagesSorter = new FixedDateImageSorter(fakeNowDate);
    server = VenueServerImplBuilder.create().withImagesSorter(imagesSorter).build();
    server.init();
    return imagesSorter;
}

If you really want to a Mockito spy, then as you said the doReturn is eagerly evaluated. So you need to use the doAnswer to lately evaluate the response:

import static org.mockito.Matchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.doAnswer;

...

final Date fakeNowDate = new Date();
final ImagesSorter imagesSorter = spy(new ImagesSorter());

doAnswer(new Answer<List>() {
    public List answer(InvocationOnMock invocation) throws Throwable {
        // Get the actual arguments
        List arg1 = (List) invocation.getArguments()[0];
        User arg2 = (User) invocation.getArguments()[1];
        // Then call the 3-args method using fakeNowDate
        return imagesSorter.sortImages(arg1, arg2, fakeNowDate);
    }
}).when(imagesSorter).sortImages(any(List.class), any(User.class));

But i don't think it's the best approach.

Upvotes: 1

Related Questions