Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mockito, verify a function is invoked 0 time(s)

I am using Mockito to write my test case. I have a simple class which contains a function countPerson(boolean) which I am interested to test:

public class School {
    //School is a singleton class.

    public void countPerson(boolean includeTeacher) {
        if (includeTeacher) {
            countIncludeTeacher();
            return;
        }
        countOnlyStudents();
    }

    public void countIncludeTeacher() {...}
    public void countOnlyStudents() {...}
}

In my unit test, I want to test the countPerson(boolean) method:

@Test 
public void testCountPerson() {    
    School mSchool = School.getInstance();
    School spySchool = Mockito.spy(mSchool);
    spySchool.countPerson(true);

    //Verify the function countIncludeTeacher is invoked once
    verify(spySchool).countIncludeTeacher();
    //Until here things are working well.

    spySchool.countPerson(false);
    //ERROR HERE when I try to verify the function has 0 times invoke
    verify(spySchool, times(0)).countIncludeTeacher();      
}

I have the following error:

org.mockito.exceptions.verification.NeverWantedButInvoked: school.countIncludeTeacher(); Never wanted here: (SchoolTest.java 20)

Why does the 0 times verify not work?

like image 474
user842225 Avatar asked Nov 12 '15 12:11

user842225


People also ask

How do you verify a method called in Mockito?

Mockito verify() simple example It's the same as calling with times(1) argument with verify method. verify(mockList, times(1)). size(); If we want to make sure a method is called but we don't care about the argument, then we can use ArgumentMatchers with verify method.

Does Mockito verify use equals?

Mockito verifies argument values in natural java style: by using an equals() method.


2 Answers

Actually, the problem is that each verify call is made on the same spySchool instance. Let me explain:

@Test 
public void testCountPerson() {    
    School mSchool = School.getInstance();
    School spySchool = Mockito.spy(mSchool); // a spy is created here, Mockito is listening in.
    spySchool.countPerson(true); // real method is invoked
    verify(spySchool).countIncludeTeacher(); // our spy identified that countIncludeTeacher was called

    spySchool.countPerson(false); // real method is invoked
    verify(spySchool, times(0)).countIncludeTeacher(); // our spy still identified that countIncludeTeacher was called, before it was called before
}

The thing is that in the latest verify, it fails because the countIncludeTeacher method was called on the spy before and that called was not deregistered.

You can do it using verifyNoMoreInteractions, which verifies that the object had no more interactions. You could also reset the object.

But note that this is really not recommended, quoting Mockito Javadoc:

A word of warning: Some users who did a lot of classic, expect-run-verify mocking tend to use verifyNoMoreInteractions() very often, even in every test method. verifyNoMoreInteractions() is not recommended to use in every test method. verifyNoMoreInteractions() is a handy assertion from the interaction testing toolkit. Use it only when it's relevant. Abusing it leads to overspecified, less maintainable tests. You can find further reading here.

And

Smart Mockito users hardly use this feature because they know it could be a sign of poor tests. Normally, you don't need to reset your mocks, just create new mocks for each test method.

Instead of reset() please consider writing simple, small and focused test methods over lengthy, over-specified tests. First potential code smell is reset() in the middle of the test method. This probably means you're testing too much. Follow the whisper of your test methods: "Please keep us small & focused on single behavior".

I would definitely suggest that you split this test in two: one for the true case and one for the false case.

like image 129
Tunaki Avatar answered Oct 18 '22 08:10

Tunaki


It fails because you actually DO invoke countIncludeTeacher() when you run spySchool.countPerson(true) in your test. This branches out to your other function, recoding as an invocation on the spy object.

In your current setup, you would either have to verify it was called only once, or reset your spy object instance before running the second test.

like image 34
kasoban Avatar answered Oct 18 '22 08:10

kasoban