简体   繁体   中英

Unit tests assert vs Mockito.verify()

Fiddling around with Mockito for implementing unit tests of my services but for some reason I can't get this through my thick skull. My tests are passing but I am not conviced that I am doing it right.

Here is an example where I test the count() method. The method simply forwards the call to its repository and I wan't to verify that only that and nothing else happens. This is what I've got:

@RunWith(MockitoJUnitRunner.class)
public class PersonServiceImplTest {

    @Mock
    private PersonRepository personRepository;

    @InjectMocks
    private PersonServiceImpl personService;

    @Test
    public void testCount() {

        when(personRepository.count()).thenReturn(2L);

        long count = personService.count();

        assertEquals(2L, count);

        verify(personRepository).count();
    }
}

My test is passing but I have some questions.

  1. Is the assertEquals needed? as I understand it, whatever I put as the expected result of the method stub (.thenReturn(value..)) will ALWAYS be the value returned. Or could it be something else in this scenario?

  2. Do I need the verify? I feel like I do because I want to verify that personRepository.count() was actually called. Or is that redundant when I have assertEquals() as well?

  3. Do I need both assertEquals and verify?

  4. And finally, am I doing this right:)

Thank you

In order:

  1. Is the assertEquals() needed: It depends. Is there anything done to the result of personRepository.count() in personService before it is returned, that has any possibility of changing its value? If the answer is "definitely not", then you might not need to assertEquals() - but if there's any chance something might go wrong, then assertEquals() will make sure it didn't.

  2. Do you need verify() : It depends. Is there any chance that personRepository.count() wasn't called? Or that it was called more than once ( verify() by default expects its argument to be called exactly once)? If not, then you might not need it.

  3. Do you need both: It depends (noticing a pattern?). See above: they do different things. There are many cases in which you want both things to be checked: 1. that the right result is returned, and 2. that the result is returned by doing the things you expect to be done.

  4. Are you doing this right: Well... It depends. Does personRepository.count() look like

     public int count() { return this.personService.count(); } 

If so, you probably don't need much testing for it at all. If you insist on having a test, skipping verify() is probably alright, because the method above has no other way of getting a value than by calling the function you would be verify ing, and it returns that value, so it could hardly call it more than once.

On the other hand, if your function looks like:

public int count() {
    // get a personService from an injector
    // log the personService's details
    // generate a random number
    // try calling count() on personService, catch an error
    // if you caught the error, return the random number
}

Then maybe you do want to verify() because all of a sudden, there are a lot of things going on and some of them (ie the random number) could be confused for correct functioning even if something is going horribly wrong.

Yes, you are doing it right.

You are injecting a mock repository into a real service, then testing the service. When it comes to business logic in your service, anything could be happening. That's why it's important to verify the code with known inputs and known outputs, just like you're doing.

  1. You're checking the result of the business logic, given the response from the repository. This particular code is fairly straightforward, but imagine if the business logic was providing an average or a sum, not just the same value provided from the repository.

  2. and 3. The verify and the assertEquals are testing different things. The verify checks that your repository method was called, the assert checks that the service responded with the correct value. Imagine that your service method had a hard-coded return 2L . The assertEquals would pass, but the verify would fail.

  3. Yes, you're doing it right. What you're testing is linked to Why you're testing. Whether a particular assertion or verify is required is usually down to the individual situation. In this case, there is little point in testing that your repository method returns 2L , but there is a good case for testing that your service returns 2L . Similarly, there is no point in testing that the service method has been called, but there's a good case for testing that the repository method has been called.

You now have the tools to test your service, the next step is determining which tests to write.

It is better to inject PersonRepository through a constructor rather than @InjectMocks . This also eliminates the need for a specific runner (or, later in your testing, involving Spring for low-level tests).

Your use of Mockito is correct but not optimal. You do need the assertEquals because what you're testing is that the service returns the same value as you are providing from the repository. The verify...count is not necessary because it's implied by checking that the appropriate value was returned. You can improve this by returning a random number instead of 2 .

Also check whether it's really worth wrapping count() in another object or if you're just adding unnecessary layers.

Finally, you might consider examining Spock ; it's a Groovy-based test language on top of JUnit that provides a clean and powerful mock language.

I can get these things from your test:

  • You are doing Unit test for Service layer (*)

     @InjectMocks private PersonServiceImpl personService;
  • Your test methodology is White-box (**)

    verify(personRepository).count();

(*) is because you are using real object for Service and mocking everything else (as @InjectMocks will try to instantiate your Service with mocked Repository as dependency)

(**) is because you care about how the system works (the call to Service.count() in-turn dispatches the call to Repository.count()).

Based on that assumption, here are answers to your questions (not in your order):

2.

Do I need the verify?

It depends on what you care about:

  • If you care about HOW a Service should work behind the scence aka doing White-box testing (eg: service must dispatch the call to Repository, not hard-code values), you DO need the verify to enforce the implementation design. In future, your test may fail and you will know that fail is as expectation or not (implementation design changed or bug fixing side effect, etc). More tests you should consider to add when taking this approach: Is log printed? Is repository.count() called for exactly once? etc. See White-box_testing

  • If you care about WHAT the result is when service.count() is called aka doing Black-box testing, you DO NOT need to verify the method call. Instead, there are more tests you should consider to add: IS service.count() 0 when repository.count() is 0? Does service.count() throw exception when repository.count() throws exception? See Black-box_testing .

    Because we have to stub the repository 's count() to test service 's count(), people usually confused this approach with white-box testing but it's not since we care about the context WHEN service.count() returns that value, not how.

  • Of course you can care about both WHAT the result is and HOW the result was calculated but it is not recommended since it will make your test complicated and hard to maintain.

Do I need both assertEquals and verify?

Again, I assumpt that you are doing white-box testing. Although you are doing white-box testing - verify is more important here, at least 1 sainity test is recommended. So you are doing correctly in this case.

1.

Is the assertEquals needed?

As explained above, yes, assertEquals is needed.

as I understand it, whatever I put as the expected result of the method stub (.thenReturn(value..)) will ALWAYS be the value returned.

  1. You are stubbing a method of Repository class, which is a dependency - not your test target (Service)
  2. You are asserting the result of your test target (Service)

So you are doing it correctly.

4.

And finally, am I doing this right:)

This is a good typical white-box test I think:)

References:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM