I want to test a class using Spring + JUnit + Mockito but I don't manage to make it work properly.
Let's say my class references a Service:
@Controller
public class MyController
{
@Autowired
private MyService service;
@PostConstruct
public void init() {
service.whatever();
}
public void doSomething() {
service.create();
}
}
And this Service references a Repository:
@Service
public class MyService {
@Autowired
private MyRepository repository;
public void whatever() {}
public void create() {
repository.save();
}
}
When testing the MyController class, I want the service to be mocked. The problem is: even when the service is mocked, Spring tries to inject the repository in the mock.
Here is what I did. Test class:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { MyControllerTestConfiguration.class })
public class MyControllerTest {
@Autowired
private MyController myController;
@Test
public void testDoSomething() {
myController.doSomething();
}
}
Configuration class:
@Configuration
public class MyControllerTestConfiguration {
@Bean
public MyController myController() {
return new MyController();
}
@Bean
public MyService myService() {
return Mockito.mock(MyService.class);
}
}
And the error I get: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [test.test.MyRepository] found for dependency
I tried to initialize the mock using Mockito's @InjectMocks
annotation but this fails because the @PostConstruct
method is called before the mocks injection, generating a NullPointerException
.
And I cannot simply mock the repository because in real life that would make me mock A LOT of classes...
Can anyone help me on this?
Use constructor instead of field injection. That makes testing a lot easier.
@Service
public class MyService {
private final MyRepository repository;
@Autowired
public MyService(MyRepository repository) {
this.repository = repository;
}
public void whatever() {}
public void create() {
repository.save();
}
}
-
@Controller
public class MyController {
private final MyService service;
@Autowired
public MyController(MyService service) {
this.service = service;
}
@PostConstruct
public void init() {
service.whatever();
}
public void doSomething() {
service.create();
}
}
This has several advantages:
NullPointerException
. [The instance fields for the dependencies] can be final, so 1) they cannot be accidentally modified, and 2) any thread reading the field will read the same value.
Discard the @Configuration
class and write a test like this:
@RunWith(MockitoJUnitRunner.class)
public class MyControllerTest {
@Mock
private MyRepository repository;
@InjectMocks
private MyService service;
@Test
public void testDoSomething() {
MyController myController = new MyController(service);
myController.doSomething();
}
}
Use interfaces, especially if you use some kind of AOP (transactions, security, etc), i.e. you'll have interface MyService
and class MyServiceImpl
.
In configuration you'll have:
@Bean
public MyService myService() {
return Mockito.mock(MyService.class);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With