Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does mockito report error on thenReturn() in java 7 but not in java 6

Mockito reports unfinished stubbing error when mocking client.getPrograms() which should return SortedSet<Program>. Interesting part is that it does so only when using Java 7 and not when using Java 6.

Here's the code that triggers error when mocking client.getPrograms():

private void prepareScheduleChangePreconditions() {
    Client client = mock(Client.class);
    TimeTable tt = BuilderUtil.buildTable(AcceleratedScheduleTimeTable.Schedule.NORMAL, "08:00");
    when(clientRepository.findByCode(anyString())).thenReturn(client);
    //Error is reported for next line of code
    when(client.getPrograms()).thenReturn(new TreeSet<Program>(Collections.singleton(program)));
    when(event.getTimeTable()).thenReturn(tt);      
}

Here's the error output:

Tests in error:
  testExampleScheduleChangeNotify1(com.example.service.impl.ExampleServiceImplTest):
Unfinished stubbing detected here:
-> at com.example.service.impl.ExampleServiceImplTest.prepareScheduleChangePreconditions(ExampleServiceImplTest.java:134)

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!

Method is not final. Any help or clues would be appreciated.

UPDATE 1

As per Mike B request, I managed to isolate this into simpler test case that fails in Java 7.

@RunWith(MockitoJUnitRunner.class)
public class MockitoTest {

    @Mock
    Program program;

    private void preparePreconditions() {
        Client client = mock(Client.class);
        when(client.getPrograms()).thenReturn(new TreeSet<Program>(Collections.singleton(program)));
    }

    public static class Client {
        public SortedSet<Program> getPrograms() {
            return new TreeSet<Program>();
        }
    }

    public static class Program implements Comparable<Program> {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public int compareTo(Program program) {
            return 0;
        }
    }

    @Test
    public void test() {
        preparePreconditions();
    }

}

UPDATE 2

Strangely enough, it works if I do it this way:

TreeSet<Program> programs = new TreeSet<Program>();
programs.add(program);
when(client.getPrograms()).thenReturn(programs);
like image 524
Krešimir Nesek Avatar asked Apr 10 '14 14:04

Krešimir Nesek


People also ask

What does thenReturn do in Mockito?

thenReturn or doReturn() are used to specify a value to be returned upon method invocation. //”do something when this mock's method is called with the following arguments” doReturn("a").

How do you use lenient?

not as strict as expected when punishing someone or when making sure that rules are obeyed a lenient sentence/fine The judge was far too lenient with him.

Does Mockito have a return method?

In Mockito, you can specify what to return when a method is called. That makes unit testing easier because you don't have to change existing classes. Mockito supports two ways to do it: when-thenReturn and doReturn-when . In most cases, when-thenReturn is used and has better readability.


2 Answers

I believe that what is really happening here is more than one mock method invocation in single when(...).then(...) statement.

Consider following example:

when(program.getName()).thenReturn(String.valueOf(program.compareTo(null)));

It returns the same exception that You have. It is because there are two methods invoked on mock: getName() and compareTo() in single when(...).thenReturn(...) statement.

Also in this page (the mockito documentation) You can read that:

By default, for all methods that return value, mock returns null, an empty collection or appropriate primitive/primitive wrapper value (e.g: 0, false, ... for int/Integer, boolean/Boolean, ...).

Therefore mockito has to have some mechanism for detecting what to do (return) for which invocation on some mocked object.

In Your example second invocation is made by new TreeSet<>(Collections.singleton(program)) statement, because TreeSet constructor is using compareTo() method of Your mock.

It seems that implementation of TreeSet changed from java 6 to java 7. That is why it could have worked earlier.

like image 184
Rafał Spryszyński Avatar answered Nov 15 '22 18:11

Rafał Spryszyński


This doesn't actually answer your question, but remember the prime directive of mocking:

  1. Mock roles, not objects

In practice, this means that mocking a class (i.e. not an interface) is a blinking red warning sign that something is wrong with your design.

If you have no control over the code you're interfacing with (e.g., it is a third-party library or legacy software), I like to wrap the class in an interface and mock that. As a bonus, this also allows you do rename poorly-named methods (and classes) in the other code. From your example, what "programs" does the client return? Is it the set of active programs, running programs, what? Let's assume that it is the set of active programs:

public interface Programs {
    SortedSet<Program> active();
}

class SimpleClientWrapper implements Programs {
    private final Client wrapped;

    SimpleClientWrapper(Client c) { wrapped = c; }

    public SortedSet<Program> active() { return wrapped.getPrograms(); }
}

If this feels like too much work (and I admit that sometimes it is overkill), another possibility, if the method(s) you want to mock are non-final, is to temporarily override them in your test:

Client client = new Client() {
    @Override
    public SortedSet<Program> getPrograms() {
        return new TreeSet<Program>(Collections.singleton(program));
    }
};

I sometimes use this approach to override error handlers and such.

like image 27
espenhw Avatar answered Nov 15 '22 17:11

espenhw