Hot questions for Using Mockito in mybatis

Question:

In my WebApp, I have used MyBatisGenerator to generate mapper interfaces. I have a class UserService as follows:

public class UserService {

    @Autowired
    UserMapper userMapper;

    public int create(UserParams userParams) {

        User user = new User();
        user.setFirstName(userParams.getFirstName());
        user.setLastName(userParams.getLastName());
        user.setUserName(userParams.getUserName());
        user.setGender(userParams.getGender());
        user.setInstitutionName(userParams.getInstitutionName());

        userMapper.insertSelective(user);
        int userId = user.getUserId();//line 10 in method.

        return userId;
     }
}

I have written a test class as follows:

@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest extends SpringBootHelloWorldTests {

    @InjectMocks
    UserService service;

    @Mock 
    UserMapper userMapper;

    @Test
    public void testUserService_create(){

        UserParams userParams=new UserParams();
        int i=service.create(userParams);
        assertNotEquals(i,0);
    }       
}

When I execute this test class I am getting a error because in the UserService class in the create method - line number 10. I am getting a NullPointerException as the userId of the user object is null.

I am aware of the fact that it's not possible to mock method local variables.

If I run this same UserService normally (in WebApp), the userMapper.insertSelective method populates the userId of the user passed into the method.

How can we mock userMapper.insertSelective method so that I can configure it to populate a userId in the user object of my test class test method?


Answer:

Assuming that insertSelective is a void method you can user doAnswer, to manipulate the argument passed to the mock. (If it is not a void method use when( ... ).thenAnswer( ... ).)

@Test
public void testUserService_create() {

    Mockito.doAnswer(new Answer<Void>() {

        @Override
        public Void answer(InvocationOnMock invocation) throws Throwable {

            User user = (User) invocation.getArguments()[0];
            user.setUserId(5);

            return null;
        }}
    ).when(userMapper).insertSelective(Mockito.any(User.class));

    UserParams userParams = new UserParams();

    int i = service.create(userParams);
    Assert.assertEquals(5, i);
}

Question:

For tests I use:

  • Spring Test 3.2.3.RELEASE
  • JUnit 4.12
  • Mockito 1.10.19

The following test code should save some entity into the database, however, this does not occur:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {ControllerTestConfig.class})
public class ControllerTest {

    @Autowired
    SomeMapper someMapper;

    @Test
    public void shouldCreateSomeEntity() {
        SomeEntity someEntity = new SomeEntity();
        someEntity.setSomeProperty("property");
        ...

        someMapper.createSomeEntity(someEntity);
    }

    ...
}   

I use a simulated implementation of the mapper:

@Configuration
public class ControllerTestConfig {

    @Bean
    public SomeMapper SomeMapper() {
        return Mockito.mock(SomeMapper.class);
    }

    ...
}

Because the implementation is simulated, the method call is intercepted in the class org.mockito.internal.creation.cglib.MethodInterceptorFilter.

The mapper is an interface:

public interface SomeMapper {

    @Insert("Insert into some_table (id, some_entity_id, type, full_name) values (#{id}, #{someEntityId}, #{type}, #{fullName})")
    @SelectKey(statement="select nextval('seqid_static_data');", keyProperty="id", before=true, resultType=long.class)
    void createSomeEntity(SomeEntity someEntity);

    ...
}

Thus, it is not possible to create an instance of this mapper. For example, by this way:

@Bean
public SomeMapper SomeMapper() {
    return new SomeMapper();
}

...

How to use MyBatis mappers in Spring's JUnit tests?


Answer:

Did you try to emulate method call by doAnswer or doThrow, they should works with void methods. For example:

@Test
public void shouldCreateSomeEntity() {
    SomeEntity someEntity = new SomeEntity();
    someEntity.setSomeProperty("property");       

    Mockito.doAnswer(invocation -> {
           invocation.getArgument(0).setSomeProperty("changed_property")
        }).when(someMapper).createSomeEntity(Mockito.eq(someEntity));

    someMapper.createSomeEntity(someEntity);
    Assert.assertEquals("changed_property", someEntity.getSomeProperty());
}

Question:

I would like to spy with Mockito if MyBatis Mapper methods are invoked and how many times, but I'm not able to do this while I'm getting the

org.apache.ibatis.binding.MapperProxy

instead of my mapper implementation. Is there any possibility to get an object from this Proxy? Or is there any other solution to spy with Mockito over Mapper methods?

MyBatis version: 3.4.1

Mockito version: 1.9.5

Spring version: 3.2.8.RELEASE


Answer:

To get object from MyBatis MapperProxy is not possible, at least I didn't found any way to obtain such an object, but it is possible to spy MyBatis Mapper using annotation @SpyBean and mockito inline:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>${mockito.version}</version>
    <scope>test</scope>
</dependency>