Aintsmartenough
Aintsmartenough

Reputation: 79

How does the Mockito Verify method works?

I was searching how to test that something was properly deleted from a database and I found this answer: https://stackoverflow.com/a/38082803/9115438 but it got me thinking,what if the delete method fails and it doesn't actually delete the object,what then? Does the verify method only checks if the delete method was called once or that it was called once and it was succesful? Because if it doesn't check that the delete was succesful then that test is not useful at all.

Upvotes: 6

Views: 25515

Answers (3)

Usurper
Usurper

Reputation: 97

In unit tests, the goal is to verify whether your unit (method or class) behaves as expected in isolation. This can be achieved using tools like Mockito, where you can use verify statements to check if a specific method is invoked with the expected arguments. For example, you can leverage ArgumentCaptor to capture and assert the arguments passed to a mocked method.

However, if you need to verify whether the data is correctly added to a database or a similar persistence layer, you should write integration tests. Integration tests provide the necessary environment, such as an actual database or a Redis connection, often set up using tools like Docker containers.

In such cases, you call the API or method with the required request payload, let it execute in a real-world scenario, and then verify the state of the database or persistence layer. Assertions can then be made based on the expected changes in the database.

Upvotes: 0

Dirk Herrmann
Dirk Herrmann

Reputation: 5939

You are referring to the example from Junit: writing a test for a method that deletes an entity?:

public void deleteFromPerson(person person) {
    person = personRepository.returnPerson(person.getId());
    personRepository.delete(person);
}

And, you are right, when mocking the delete method during unit-testing you will only figure out if delete was called, not whether the deletion was successfull. What are the two possibilities why delete could be unsuccessfull?

a) delete is not called correctly, maybe some argument is missing or something needs to be done in preparation to the delete. Such a problem can not be found with unit-testing: If your assumptions about how to call another component are wrong, and you mock the other component, then the way you implement the mocking will simply reflect your own misunderstanding, and the unit-tests will succeed. This, however, is not a flaw of unit-testing: It is the reason why in addition to unit-testing there exist additional levels like integration-testing, which aim at finding those bugs which unit-testing can not find. Integration testing is in fact aiming at finding bugs in the interactions between components.

b) delete might sometimes fail during run-time, no matter if you call the delete method correctly or not. For example, your code might not have write access to the personRepository, or some parallel thread has deleted the person in the meantime or whatever. The example code, however, does not have any measures in place to deal with such a run-time scenario (well, it is only a piece of example code, but it may also intentionally be like this, see davidxxx's comment). But, let's assume there should be some code to handle the unsuccessfull delete.

When doing mocking properly (namely by looking at the specification of delete), then already during unit-testing it could have become apparent that delete could fail. In that case, maybe, it would return an error code or throw an exception. The developer, when realising this, could decide to extend the above example code by the respective error handling code. And, the error handling code could be unit-tested by mocking delete such that also those error scenarios would be exercised.

If, in contrast, the developer does not realize that delete can fail, then, again, we have an integration problem at first: The developer's unit-test will be implemented under the assumption that delete could never fail. And this misconception will not be found during unit-testing with the (incompletely) mocked delete. Again, it will have to be encountered during integration testing that delete might fail during run-time. Then, again, the example code will have to be extended, the unit-tests will be extended etc.


EDIT: The above is meant to explain the relationship between unit-testing and integration testing and the role of mocking. What davidxxx correctly points out is, that for this piece of example code unit-testing will not have much value: The code essentially consists of interactions - there are no computations where unit-testing could catch bugs. Therefore, for this example code, testing should start with integration testing right away.

Upvotes: 3

davidxxx
davidxxx

Reputation: 131316

Your point is relevant.
Mockito.verify() will just verify that an invocation was done on a mock during the method execution.
It means that if the real implementation of the mocked class doesn't work as it should, the test is helpless.
As a consequence, a class/method that you mock in a test have also to be unitary tested.
But does it mean that verify() is always fine ? Not really.

Suppose a method to test that deletes something :

public class Foo{
    MyDao myDao;

    public void delete(int id){
       myDao.delete(id);
    }
}

The test passes :

@Mock
MyDao myDaoMock;
Foo foo;

@Test
public void delete(int id){
   foo.delete(id);
   Mockito.verify(myDaoMock).delete(id);
}

Suppose now I change the implementation as :

public void delete(int id){
   myDao.delete(id);
   myDao.create(id);
}

The test is still green... Oooops.

Other scenario, suppose a method that mainly invokes dependencies methods :

public void doThat(){
   Foo foo = fooDep.doThat(...);
   Bar bar = barDep.doThat(foo);
   FooBar fooBar = fooBarDep.doThat(foo, bar);
   fooBis.doOtherThing(...);
   // and so for
}

With the verifying approach, the unit test will just describe/copy past the implementation of your method in a Mockito format.
It asserts nothing in terms of returned result. Changing the implementation in a bad way (adding an incorrect invocation or removing a required invocation) is hard to detect with a test failure like the test becomes just a reflect of the invoked statements.

Mock verifying is often something to use with cautious.
In some specific cases, it may be helpful but in many cases, I have seen developers abuse of that (75% or more of the unit test class is mock setup/record/verify) and as a consequence it produces a bloat unit test with few value, very hard to maintain and that also slows down your builds for not fair reasons.
In fact, for methods to test that rely essentially on functions with side effects, integration tests (even sliced/partial) should be favored.


Mocks Aren't Stubs of Martin Fowler is an excellent post that should interest you.

This has particularly struck me when I've observed a mockist programmer. I really like the fact that while writing the test you focus on the result of the behavior, not how it's done. A mockist is constantly thinking about how the SUT is going to be implemented in order to write the expectations. This feels really unnatural to me.

While this Martin Fowler post is interesting, I don't agree with all either.
I agree that a mockist approach where developers don't mock a dependency because the dependency is annoying but does that systematically is generally a bad idea.
We should always have a good reason to introduce a mock such as :

  • external system dependency
  • invocation slowness
  • reproducibility issue
  • complex setup to interact with the dependency (input and or output)

But I disagree that creating explicitly stubs is generally a good idea because the stub code takes time to write, may have bugs and we will have to maintain that. At last to make things clean robust, stubs classes should also be unitary tested. All that has a cost.
On the other hand, mocks produced by mocking libraries don't have all these defects.
And nothing prevents us from using these mocks with a stub mind : make the mocks to collaborate as stubs, that is :

  • yes for input/output recording
  • yes for few and relevant verify()
  • but no for verify() abuses
  • and no for multiplication of fine grained mock behavior recording that couples the test to the implementation

Upvotes: 7

Related Questions