Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to mock Spring-Data-JPA repositories with Mockito

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).

like image 346
Christopher Z Avatar asked Jun 06 '16 11:06

Christopher Z


1 Answers

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).

like image 79
g00glen00b Avatar answered Nov 25 '22 17:11

g00glen00b