[JAVA] How to write Mockito

Since the page that introduces the description method of Mockito when testing with JUnit + Mockito is apt, I organized it in my own way.

environment

Features of Mockito

Mock

It is used when you want to mock and replace all the methods of the target instance. In the default state, for objects, methods with a return value will return null. (In Collection, it's an empty Collection, in primitive values it's 0 or false ...)

It can be mocked by adding the @ Mock annotation. Also, by assigning @InjectMocks to the instance of the class to be tested, You can plug a mock instance of @ Mock into an instance of a @ Inject member in an instance.

Spy

Use this when you want to replace only a specific method of the target instance. I'm not very good at it. Is there a case where you want to replace only one method while using the getter and other methods as they are?

It can be used by annotating @Spy. You need to create the instance yourself. It cannot be injected with @InjectMocks.

Captor

Primarily used to capture and validate argument values. If the argument is an object, it cannot be verified with a standard matcher such as ʻeq`. At this time, Captor is effective.

When capturing arguments, define an instance of the ArgumentCaptor class and give @Captor.

How to write a basic test

Assuming that you are designing using DI, we will summarize the basic description using Mockito.

Basic policy

--Set the mode of Mockito to Strict Stubs. --Mockito has three modes: Silent, Strict (v2 default), and StrictStubs. --If you set it to StrictStubs, it will also detect the argument mismatch of the mock that becomes the stub, so set it to the strictest. --In principle, initialization is done with annotations. --There is also an initialization method using mock (XX.class), spy (new XX ()), ʻArgumentCaptor.forClass (XX.class), but it is simpler to write using annotations. --Be careful not to target the interface to which @InjectMocksis added. (cannot new) --You don't have to writeverify. --From v2, if the prepared mox tab is not called, an unchecked exception will occur, so I don't think it is necessary to verify with verifyeach time it is called. --If so, what if you want to count the number of times you have been called multiple times? --Write the mock description in the formatdoXX (). When ()`. --There are pros and cons here, and it seems that official has not revealed its attitude. Is.

you may prefer to use these methods in place of the alternative with when(),
for all of your stubbing calls.

And that. ――Personally, it is troublesome to learn the description method one by one depending on the presence or absence of the return value and the difference between Mock and Spy. I think it will be complicated to look at. --Therefore, unify to the format of doXX (). When () that can be used in all descriptions. --However, in the case where the return value is changed by the call described later, the description in when (). ThenReturn () format is required.

Preparation

  1. Give the magic @RunWith (MockitoJUnitRunner.StrictStubs.class) test class to use Mockito in the test. --You may make the same setting with @Rule. (Strictness.STRICT_STUBS)
  2. Add @InjectMocks to the variable of the class to be tested.
  3. Add @ Mock to the variable of the class you want to mock. -Just add @InjectMocks and @Mock annotations to create an instance.
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
...

@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class XXTest{
  @InjectMocks
  XXInteractor target;

  @Mock
  IXXRepository repoMock;

  ...
}

test case

  1. Define a void public method with @Test.
  2. Describe the definition of the method you want to mock the mock instance.
  3. Call the method under test.
  4. Verify the result.

When the test target is as follows

public class XXInteractor{
  @Inject
  IXXRepository repository;

  XXEntity fetch(String id) throws NotFoundException{
    try{
      return repository.findBy(id);
    }
    catch(UseCaseNotFoundException e){
      throw new NotFoundException();
    }
  }
}

You can write the following test.

  private static final String ID = "ID";

  @Test
  public void testFetchSuccess() throws Exception{
    //Returns a normal XXEntity only when an ID is given as an argument.(Otherwise, null)
    doReturn(new XXEntity(ID))
      .when(repoMock).findBy(eq(ID));

    XXEntity result = target.fetch(ID);

    assertThat(result.getId(), is(ID));
  }

  @Test(expected = NotFoundException.class)
  public void testFetchFailureWithNotFound() throws Exception{
    doThrow(new RepositoryNotFoundException())
      .when(repoMock).findBy(eq(ID));

    target.fetch(ID);
  }

Mocked use case

Basic edition

The basic mocking pattern is shown below.

Returns a value with a mocked method

//If there is a return value
doReturn(new XXEntity(ID))
  .when(targetMock).fetch(eq(ID));

//If there is no return value
doNothing().when(targetMock).delete(eq(ID))

Raise an exception in a mocked method

doThrow(new XXException()).when(targetMock).fetch(eq(ID))

doThrow(new XXException()).when(targetMock).delete(eq(ID))

Replace the method of the Spy instance

//Returns a return value
doReturn(new XXEntity()).when(targetSpy).fetch()

//Raise an exception
doThrow(new XXException()).when(targetSpy).fetch()

Advanced version

I want to respond if any argument is called

doReturn(new XXEntity(ID))
  .when(targetMock).fetch(any())

I want to return a different value for each call with the same method

//Cases with no arguments, or cases where you want to return a different value for each call with the same arguments
//This case cannot be written with doReturn, so describe it in thenReturn format.
when(targetMock.getNextKey())
  .thenReturn("ID-1")   //First call
  .thenReturn("ID-2");  //Second call

//Case that returns a different value for each argument(Define each normally)
doReturn(new XXEntity("ID-1")).when(targetMock).fetch(eq("ID-1");
doReturn(new XXEntity("ID-2")).when(targetMock).fetch(eq("ID-2");

I want to validate non-primitive arguments

If the argument is primitive, it can be verified only with ʻeq`, but if the argument is an object or list map system, it cannot be verified, so it must be combined with Captor.

When the test target is as follows

public class XXInteractor{
  @Inject
  IXXRepository repository;

  void update(XXEntity entity){
    repository.update(entity);
  }
}

Verify as follows.

  private static final String ID = "ID";

  @InjectMocks
  XXUseCase target;

  @Mock
  IXXRepository repoMock;

  //Define Captor
  @Captor
  ArgumentCaptor<XXEntity> argCaptor;

  @Test
  public void testUpdateSuccess(){
    //Define a mock to capture the arguments
    doNothing().when(repoMock).update(argCaptor.capture());

    target.update(ID);

    //Validate the captured arguments
    assertThat(argCaptor.getValue().getId(), is(ID));
  }

I want to rewrite the arguments that came to the mock

A case of mocking a method that implements something like ref in C # that uses part of the argument as the return value. Since this method definition is not Testable, it should be basically solved by design change, If you really want to rewrite the contents of the instance received as an argument, you can do it by the following method.

doAnswer( invocation -> {
  Object[] args = invocation.getArguments();

  //You can manipulate that instance by casting the arguments to that class.
  //In this case, the second argument is rewritten, so args[1]
  ((XXOutputParam) args[1]).setId("ID");
  return null; //Null if the return value is void
}).when(useCaseMock).get(any(), any())

I want to reset the movement of the mock

Call it every time around the @Before method.

reset(mock);

reference

https://static.javadoc.io/org.mockito/mockito-core/2.23.4/org/mockito/Mockito.html

Recommended Posts

How to write Mockito
How to write Rails
How to write dockerfile
How to write docker-compose
How to write migrationfile
How to write good code
Bit Tetris (how to write)
How to write java comments
[Refactoring] How to write routing
Great poor (how to write)
[Note] How to write Dockerfile/docker-compose.yml
How to write Junit 5 organized
How to write Rails validation
How to write Rails seed
[Ruby] How to write blocks
How to write Rails routing
Studying Java # 6 (How to write blocks)
[Rails] How to write in Japanese
Baseball ball count (how to write)
Rails on Tiles (how to write)
[Rails] How to write exception handling?
How to write Java variable declaration
Y-shaped road tour (how to write)
How to write easy-to-understand code [Summary 3]
[RSpec] How to write test code
How to deploy
[Basic] How to write a Dockerfile Self-learning ②
Summary of how to write annotation arguments
[Introduction to Java] How to write a Java program
[Java] How to output and write files!
How to write Spring AOP pointcut specifier
How to write an RSpec controller test
[SpringBoot] How to write a controller test
How to write and explain Dockerfile, docker-compose
JDBC promises and examples of how to write
How to mock each case with Mockito 1x
How to develop OpenSPIFe
[JavaFX] How to write Eclipse permissions in build.gradle
How to write offline 15th reference question answer
How to call AmazonSQSAsync
How to use Map
Java Development Basics ~ How to Write Programs * Exercise 1 ~
How to use with_option
How to use fields_for
How to use java.util.logging
How to use map
How to use collection_select
JUnit 5: How to write test cases in enum
How to adapt Bootstrap
How to use Twitter4J
How to use active_hash! !!
How to install Docker
How to use MapStruct
How to use TreeSet
How to uninstall Rails
How to install docker-machine
[How to use label]
How to make shaded-jar
How to use hashes
Offline real-time how to write F06 implementation example
How to write code that thinks object-oriented Ruby