Hot questions for Using Mockito in spring data

Question:

I am trying to write tests for a service. But I am unsuccessful at mocking the repository dependency. Other non-repository dependencies are successfully mocked. The repository instance is always the actual implementation and not a mock instance.

I am using Spring Boot and Spring Data JPA to build the application. Mockito is used for mocking. I managed to distil the problem into a test project. The complete test project is on GitHub. Below are snippets of code from the test project; the following is the PersonServiceTest class.

Update 1: before() code should be checking personRepository not personService

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(App.class)
@WebAppConfiguration
@TestExecutionListeners({ServletTestExecutionListener.class, DirtiesContextBeforeModesTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class, SqlScriptsTestExecutionListener.class})
@Transactional
@ActiveProfiles({"mock-some-bean", "mock-person-repository"})
public class PersonServiceTest {

    @Inject
    private SomeBean someBean;
    @Inject
    private PersonRepository personRepository;
    @Inject
    private PersonService personService;

    @Before
    public void before() {
        assertThat(mockingDetails(someBean).isMock(), is(true));
        assertThat(mockingDetails(personRepository).isMock(), is(true));
    }

    @Test
    public void doSomething() throws Exception { ... }
}

The test class uses two profiles: mock-some-bean and mock-person-repository. Basically, I am using profiles to determine what should be mocked. Before doing any tests, I assert that the someBean and personService are mocked instances. someBean is mocked properly but personService always fails. The following code is the code from the TestConfig class.

@Configuration
public class TestConfig {

    private static final Logger logger = LoggerFactory.getLogger(TestConfig.class);

    @Bean
    @Profile("mock-some-bean")
    public SomeBean someBean() {
        logger.info("Mocking: {}", SomeBean.class);
        return mock(SomeBean.class);
    }

    @Bean
    @Profile("mock-person-repository")
    public PersonRepository personRepository() {
        logger.info("Mocking: {}", PersonRepository.class);
        return mock(PersonRepository.class);
    }
}

Update 2: question made clearer

What am I missing? It appears that Spring Data JPA is always creating an instance and ignoring the @Bean defined in the TestConfig class. How do I "tell" Spring Data JPA not to create an instance? I appreciate any help I can get to resolve this problem.

Update 3: still looking for ideal solution

I would still appreciate a solution. Although I have marked the solution as accepted, the suggested solution is not ideal. Because there are varying levels of integration tests (from end-to-end testing to a very narrow scope of testing with a small set of dependencies).


Answer:

There are a few issues here. I assume that you're trying to verify if PersonRepository is mocked or not. However, in your test you wrote:

assertThat(mockingDetails(personService).isMock(), is(true));

You're not mocking PersonService so it makes sense that it would fail this assertion.


Another issue is that Spring Data JPA will also create a bean called personRepository. So your mocked repository bean will be ignored unless you change its name:

@Bean
@Profile("mock-person-repository")
// Change method name to change bean name
public PersonRepository personRepositoryMock() {
    logger.info("Mocking: {}", PersonRepository.class);
    return mock(PersonRepository.class);
}

But if you do that, then there will be two beans of the type PersonRepository, so autowiring it into your service will fail. To fix that you would have to prevent it from creating the repositories with Spring Data JPA.


Anyhow, a much cleaner solution would be to use MockitoJUnitRunner in stead of Springs test runner. All you would have to do is annotate the class you want to test with @InjectMocks and all dependencies you want to mock and inject with @Mock: like this:

@InjectMocks
private PersonService personService;
@Mock
private SomeBean someBean;
@Mock
private PersonRepository repository;

And then you can delete TestConfig and remove all annotations on your test and replace them with @RunWith(MockitoJUnitRunner.class).

Question:

I'm writing unit tests for a SystemLoggingService and mocking all calls to it's repository. I'm using Spring with a JPA repository.

@Repository
public interface SystemLoggingRepository extends PagingAndSortingRepository<SystemLogEntity, Long>, JpaSpecificationExecutor<SystemLogEntity> {
}

For a unit test of the service method findAll(Searchable searchable,Pageable pageable) I need to mock the repository method findAll(Specification<T> spec, Pageable pageable)

As one sees, the Searchable object gets transformed to a JPA Specifications object within the service logic.

The problem is that the JPA's Specifications class which my service logic will pass over to repository method does not implement equals().

In other words, I cannot mock the repository method very precisely as I have to use Matchers.any(Specifications.class)

BDDMockito.given(systemLoggingRepository.findAll(Matchers.any(Specifications.class), Matchers.eq(pageRequest))).willReturn(...)

How bad is this for the quality of the unit test or is this common practice? What would be a different approach to this problem? The Specifications object is a Spring framework class. It's not an option to just add the equals() method.


Answer:

You may try to capture Specifications with:

ArgumentCaptor<Specifications> specificationsCaptor = ArgumentCaptor.forClass(Specifications.class);
BDDMockito.given(systemLoggingRepository.findAll(specificationsCaptor.capture(), Matchers.eq(pageRequest))).willReturn(...)

and then verify captured value:

Specifications capturedSpecifications = specificationsCaptor.getValue();
assertThat(capturedSpecifications.getSomeProperty(), ... )

You can find more info here: https://static.javadoc.io/org.mockito/mockito-core/2.8.47/org/mockito/Mockito.html#15

Question:

I'm trying to test the following controller:

@GetMapping("movies")
public Page<Title> getAllMovies(@PageableDefault(value=2) Pageable pageable){        
    return this.titleService.getTitleByType("Movie", pageable);
}

and here's the test class:

@RunWith(SpringRunner.class)
@WebMvcTest(TitleController.class)
@EnableSpringDataWebSupport
public class TitleControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private TitleService titleService;

    // Test controller method - getAllMovies
    @Test
    public void getAllMovies() throws Exception {
        Title title = new Title();
        title.setId((short)1);
        title.setName("The Godfather");
        title.setType("Movie");    

        List<Title> titles = new ArrayList<>();
        titles.add(title);
        Page<Title> page = new PageImpl<>(titles);

        given(this.titleService.getTitleByType("Movie", PageRequest.of(0,2))).willReturn(page);
        mockMvc.perform(MockMvcRequestBuilders.get("/movies"))
                .andExpect(status().isOk());
    }
}  

When i run the test it fails and gives me the following message:

java.lang.AssertionError: Status 
Expected :200
Actual   :500

When i test the url http://localhost:8080/movies it works properly.


Answer:

I think you didn't properly mock/initialize your TitleService that's why you are getting 500 response code.

You can fix it by mocking the TitleService and passing it to your tested controller:

@RunWith(SpringJUnit4ClassRunner.class)
public class TitleControllerTest {

    private MockMvc mockMvc;

    private TitleController underTest;

    @Mock
    private TitleService titleService;

    @Before
    public void init() {
        underTest = new TitleController(titleService);

        //DO THE MOCKING ON TITLE SERVICE
        // when(titleService.getTitleByType()) etc.

        mockMvc = MockMvcBuilders
                .standaloneSetup(underTest)
                .build();
    }

    //your tests


}  

Or:

@RunWith(SpringRunner.class)
@WebMvcTest(TitleController.class)
@EnableSpringDataWebSupport
public class TitleControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private TitleController titleController;

    @MockBean
    private TitleService titleService;

    @Before
    public void init() {
        titleController.setTitleService(titleService);

        //DO THE MOCKING ON TITLE SERVICE
        // when(titleService.getTitleByType()) etc.
    }

    //your tests

}  

Question:

I am new to Junits and Mockito, I am writing a Unit test class to test my service class CourseService.java which is calling findAll() method of CourseRepository.class which implements CrudRepository<Topics,Long>

Service Class

@Service
public class CourseService {

    @Autowired
    CourseRepository courseRepository;

    public void setCourseRepository(CourseRepository courseRepository) {
        this.courseRepository = courseRepository;
    }

    public Boolean getAllTopics() {

        ArrayList<Topics> topicList=(ArrayList<Topics>) courseRepository.findAll();
        if(topicList.isEmpty())
        {
            return false;
        }
        return true;
    }
}

Repository class

public interface CourseRepository extends CrudRepository<Topics,Long>{

}

Domain class

@Entity
@Table(name="Book")
public class Topics {

    @Id
    @Column(name="Topicid")
    private long topicId;

    @Column(name="Topictitle",nullable=false)
    private String topicTitle;

    @Column(name="Topicauthor",nullable=false)
    private String topicAuthor;

    public long getTopicId() {
        return topicId;
    }
    public void setTopicId(long topicId) {
        this.topicId = topicId;
    }

    public String getTopicTitle() {
        return topicTitle;
    }
    public void setTopicTitle(String topicTitle) {
        this.topicTitle = topicTitle;
    }
    public String getTopicAuthor() {
        return topicAuthor;
    }
    public void setTopicAuthor(String topicAuthor) {
        this.topicAuthor = topicAuthor;
    }
    public Topics(long topicId, String topicTitle, String topicAuthor) {
        super();
        this.topicId = topicId;
        this.topicTitle = topicTitle;
        this.topicAuthor = topicAuthor;
    }
}

Following is the Junit class I have written but courseRepository is getting initialized to NULL and hence I am getting NullPointerException.

public class CourseServiceTest {

    @Mock
    private CourseRepository courseRepository;

    @InjectMocks
    private CourseService courseService;

    Topics topics;

    @Mock
    private Iterable<Topics> topicsList;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(CourseServiceTest.class);
    }
    @Test
    public void test_Get_Topic_Details() {

        List<Topics> topics = new ArrayList<Topics>();
        Mockito.when(courseRepository.findAll()).thenReturn(topics);
        boolean result=courseService.getAllTopics();
        assertTrue(result);
    }
}

Answer:

Change the setUp() method to:

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
}

Question:

I can't do the save to the objects after modifications.

I'm setting attributes of an object to a new values , when I'm looking to save , I receive a NullPointerException.

/* La méthode à tester */
public Rem afterInitRem(Rem rem) {

    /** Initialize regles with status MISSING **/
    List<Regle> regles = rem.getRegles();
    Regle regle = new Regle();
    regle.setCode("REGLE1");
    regle.setStatus(RegleStatus.MISSING);
    regles.add(regle);
    return remRepository.save(rem);
}

/*Le test*/
@Mock
private RemRepository remRepository;

@BeforeEach
void beforeEachTest() {
    rem = new Rem();
}

@AfterEach
void reInitVar() {
    beforeEachTest();
}

@Test
public void afterInitRemTest() {
    target.afterInitrem(rem);
    when(remRepository.save(any(Rem.class))).thenReturn(rem);
    ArgumentCaptor<Rem> argument = ArgumentCaptor.forClass(Rem.class);
    verify(regleRepository).save(argument.capture());

    assertEquals("REGLE1", argument.getValue().getRegles().get(0).getCode());
    assertEquals(RegleStatus.MISSING, argument.getValue().getRegles().get(0).getStatus());
    assertEquals(1, argument.getValue().getRegles().size());
}

Je fais le debug, je vois que les sets sur les attributs ça passent, le problème est au niveau du return remRepository.save(rem);


Answer:

As you are using Junit5, make sure you have either of these in your code to initialize the mocks:

@ExtendWith(MockitoExtension.class) on the test class

or

@BeforeEach
void beforeEachTest() {
    MockitoAnnotations.initMocks(this);
    rem = new Rem();
}

Also, you need to make sure that you have injected your mock manually or by using the @InjectMocks annotation into the SUT.

And finally, make sure you do all the mock set-up before calling the actual SUT method.

Question:

I've below void method which I am looking to get mock of.

public void updateEmployee(EmployeeDto dto) {
    Employee d = convertToEntity(dto);
    employeeRepository.updateEmployee(d.getEmployeeName(), d.getEmployeeDescription(),
            d.getEmployeeOwnerEmployeeId(), d.getEmployeeCode(), d.getStatus());
}

But I am getting below error.

org.mockito.exceptions.misusing.NotAMockException: 
Argument passed to when() is not a mock!
Example of correct stubbing:
    doThrow(new RuntimeException()).when(mock).someMethod();
    at com.xxx.EmployeeServiceTest.test_UpdateEmployee(EmployeeServiceTest.java:120)
    at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:68)
    at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:89)
    at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:97)
    at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:87)
    at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:50)
    at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
    at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)

Test method

@RunWith(PowerMockRunner.class)
@PrepareForTest({StatusEnum.class})
public class EmployeeServiceTest {

    @Mock
    private Employee employeeMock;

    @InjectMocks
    private EmployeeServiceImpl employeeServiceImpl;

    @Mock
    private EmployeeRepository employeeRepositoryMock;

    @Mock
    private EmployeeDto employeeDtoMock;

    @Mock
    private StatusEnum statusEnum;

    @Mock
    private Exception ex;

    List<String> employeeNames = new ArrayList<>();

    @Before
    public void setup() {
        // To mock static methods or class
        mockStatic(StatusEnum.class);       
    }


    @Test
    public void test_UpdateEmployee() {
        doNothing().when(employeeServiceImpl).saveEmployee(any(EmployeeDto.class));
        employeeServiceImpl.updateEmployee(employeeDtoMock);

        /*doAnswer((i) -> {
            System.out.println("Employee setName Argument = " + i.getArgument(0));
            assertTrue("Pankaj".equals(i.getArgument(0)));
            return null;
        }).when(employeeServiceImpl).updateEmployee(employeeDtoMock);*/
    }
}

Answer:

The exception here seem clear to me, employeeServiceImpl seems not to be a mock. How did you instantiate it ? Using Mockito.mock or @Mock on the field ?

--- Edit

To clarify my answer, in the @Before (or equivalent in your test class) instantiate your service with mockito :

this.employeeService = Mockito.mock(EmployeeService.class);

or

@Mock
private EmployeeService employeeService;

And then it should work.

--- Edit So seeing how you inject your mock I think I might get what's happening. In fact @InjectMocks does not make your EmployeeService a mock. It allows mockito to know let your framework create the bean and injects the mocks you have created in it.

Here if you have declared your repository as a mock like this

@Mock
private EmployeeRepository employeeRepository;

Then a mock of type EmployeeRepository will be injected inside the employeeService instance, which is not a mock.

Then if it is in fact the repository you want to mock, you should put it in the when in your tests like :

    doNothing().when(employeeRepository).saveEmployee(any(EmployeeDto.class));
employeeServiceImpl.saveEmployee(employeeDtoMock);

Then calling your service will go into your service saveEmployee function, but when it would reach the repository which is a mock it would work as expected.

If it is in fact the whole service you want to mock then instantiate it using @Mock instead of @InjectMocks.

Hope my answer is clear !

Question:

In My project I wrote a repository class for that i need to write in-memory test class. My Repository code is as follows.

package org.jaap.reference.repository;

import java.util.List;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.stereotype.Repository;

import org.jaap.entity.AccountType;

/**
* Repository for type
*
*/
@Repository
public interface AccountTypeRepository
    extends JpaRepository<AccountType, Integer>, QueryDslPredicateExecutor<Type> {
/**
 * @param AccountTypeCode
 * @return List<Type>
 */

@Query("select T from AccountType T where T.AccountTypeCode not in ?#   {@environment.getProperty('commit.types').split(',')}")
List<AccountType> findByAccountTypeCodeNotIn(); 

}

for this I need to write unit test case using junit, mockito can anyone help me?


Answer:

Hope this code example will help.

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes=AddressBookConfiguration.class)
    public class AddressServiceTests {

      @Autowired
      private AddressService addressService;

      @Test
      public void testService() {
        Address address = addressService.findByLastName("Sheman");
        assertEquals("P", address.getFirstName());
        assertEquals("Sherman", address.getLastName());
        assertEquals("42 Wallaby Way", address.getAddressLine1());
        assertEquals("Sydney", address.getCity());
        assertEquals("New South Wales", address.getState());
        assertEquals("2000", address.getPostCode());
      }
    }