I'm trying to write a unit test for my PersonController class, which uses a dependency org.modelmapper.ModelMapper. The problem is that when the test calls a method of PersonController that uses the ModelMapper, the ModelMapper is null which results in an error. Clearly it is not being autowired correctly when running the test, but it works when running the application. I am hoping there is a way to fix this problem without needing to add the ModelMapper as a constructor parameter.
The code snippets are shown below:
#SpringBootTest
class PersonControllerTest {
#Mock
private IPersonService personService;
#InjectMocks
private PersonController underTest;
#BeforeEach
void setUp() {
underTest = new PersonController(personService);
}
...Tests...
}
And the relevant PersonController code is:
public class PersonController {
#Autowired
private ModelMapper _modelMapper;
private final IPersonService _personService;
public PersonController (IPersonService personService) {
_personService = personService;
}
...Code that calls _modelMapper...
}
One problem here is that you are creating a controller class manually using new PersonController(personService) so spring annotation #Autowired does not have any effect here (because spring is not creating the class is not configuring it).
Another issue is that you are using mockito #InjectMocks which basically injects mocks (for the unit test) but at the same time, you are using #SpringBootTest which is for integration test (application is started as normal but in test mode).
Based on your description is not clear to me if you are trying to code a unit test or an integration test. so:
If you are coding a unit test, remove #SpringBootTest add ModelMapper as a constructor parameter and instantiate a class with two mocks.
If you are coding an spring integration test, remove #InjectMocks and manual class instantiation of class and flag controller as #Autowire in test.
First of all you should annotate your class PersonControllerTest with
#ExtendWith(SpringExtension.class)
class PersonControllerTest {
or
#ExtendWith(MockitoExtension.class)
class PersonControllerTest {
depending on your use case. This should already fix your problem. If you are still using Junit4 you should use
#RunWith(Springrunner.class)
class PersonControllerTest {
but since you are using
#BeforeEach
that should not be the case :)
EDIT:
When you use ModelMapper via #Autowired you have to use #ExtendWith(SpringExtension.class) . You also have to annotate your class with #SpringBootTest or import the classes that should be available via dependency Injection manually using the #Import annotation.
Related
I need to write an e2e test on REST level, that sends real requests. So I want to use application context instead of mocking beans.
RestController.class has an autowired MyService.class, and this MyService.class is dependent on two repository classes. So I tried to mock repositories and inject them in the real Service in the following way:
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = MyService.class)
#AutoConfigureMockMvc
class MyControllerTest {
#Mock private MyRepository repository;
#Mock private AnotherRepository anotherRepository;
#Autowired #InjectMocks private MyService service;
#InjectMocks private MyController controller;
#RepeatedTest(1)
void someTest() throws Exception {
MockHttpServletResponse response =
mvc.perform(...); assertThat(...);
}
}
#Service
#RequiredArgsConstructor
public class MyService {
private final MyRepository repository;
private final AnotherRepository another; ...}
But I get the following error:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myRepository'
I also tried to use #ContextConfiguration(classes = {MyConfig.class }) with no success:
#EnableWebMvc
#Configuration
public class MyConfig {
#Autowired private MyService service;
#Mock private MyRepository repository;
#Mock private AnotherRepository another;
}
Is there something I'm missing?
First off you should not use #Mock which is relevant for plain unit testing with mockito, use #MockBean instead. #MockBean "belongs" to the spring universe, it will create a bean (backed by mockito after all) but also will place it into the application context. If there is another bean of this type - it will substitute it.
I also don't think you should use #ExtendWith because #SpringBootTest in the relatively recent spring boot versions already has it defined (check the source code of #SpringBootTest annotation to make sure that's the case indeed.
Now other mistakes are more complicated.
To start with you don't need a spring boot test at all here. It also has a wrong parameter actually (which should point on a configuration class).
You should use #WebMvcTest instead. It seems that you only want to test the controller, and mock the service. #SpringBootTest in general tries to mimick the loading of the whole application, whereas the #WebMvcTest only tests a web layer. BTW, with this approach you don't need to even mock the Repository class because the controller will use the mock of the service, and since you don't load the whole application context there is no need to define a repository at all (the service being a mock doesn't use it anyway)
Add #RunWith(SpringRunner.class) to class MyControllerTest
#RunWith(SpringRunner.class)
class MyControllerTest {
}
I have a #Service called UserServiceImpl that depends on two other beans. One is the UserRepository bean and the other is a bean called SessionService.
My requirement is during tests of the UserServiceImpl class, I must be able to inject a mock of the SessionService dependency, but keep the UserRepository dependency as it is.
My service class that looks like this:
#Service
#Slf4j
public class UserServiceImpl implements UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private SessionService sessionService;
#Override
public User create(User user) {
log.info("User Creation at Service");
// ... Do some validations .. //
// This needs to be mocked in Unit Tests
String returnValue = sessionService.doSomethingThatIDontWantInTests();
user.setInternalKey(returnValue);
// .. Do some more Validations .. //
return userRepository.save(user);
}
}
Now, here is my Test Class:
#SpringBootTest
class UserServiceTest {
#InjectMocks
private UserServiceImpl userService;
#Mock
private SessionService sessionService;
#Test
void CreateUserTest() {
Mockito.when(sessionService.doSomethingThatIDontWantInTests()).thenReturn("abcxyz123321");
User user = new User();
user.setName("John Doe");
user.setEmail("john.doe#example.com");
User savedUser = userService.create(user);
assertNotNull(savedUser.getUserId());
}
}
When I run this test, Mockito successfully mocks the SessionService call. But, UserServiceImpl.createUser() still fails with the message saying:
java.lang.NullPointerException: Cannot invoke "com.myproject.data.repos.UserRepository.save(User)" because "this.userRepository" is null
Should I inject UserRepository also as a mock and use Mockito to mock the UserRepository.save() method?
I would like to mock only the SessionService dependency, and not the UserRepository dependency.
Is it something doable? If so, how? Please advise.
Thanks,
Sriram
You are mixing different testing styles.
Style 1 - spring integration test
This is when Spring Boot creates the beans in its context, and you inject them to your test class.
use #SpringBootTest
use #Autowired to inject beans to your test
use #MockBean to replace beans in Spring contexts by mocks
Style 2 - Unit test
This does not use Spring DI. In this style, it is typical to mock all dependencies.
use #ExtendWith(MockitoExtension.class)
annotate dependencies as #Mock
annotate SUT with #InjectMocks
Using real dependencies is also possible, but in that case you need to construct SUT manually - Mockito does not support partial injections.
Unit tests tend to be much more lightweight and less brittle, on the other hand integration tests cover a large chunk of the app.
Note that if UserRepository is a spring-data repo, it cant be created manually.
You must use a #SpyBean (https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/SpyBean.html), which will inject the original bean but which you can verify the calls and arguments.
#RunWith(SpringRunner.class)
#SpringBootTest(classes=MyApplication.class)
#TestPropertySource(locations = "classpath:test-application.properties")
#WebAppConfiguration
#RestClientTest(Controller.class)
public class MyIntegrationTest {
}
when I run this I get the following error
java.lang.IllegalStateException: Configuration error: found multiple declarations of #BootstrapWith for test class
The root cause is you are using both #SpringBootTest and #RestClientTest together.
Because it causes the conflict about #BootstrapWith annotation. You can see the image below.
Tip: If you are using JUnit 4, don’t forget to also add
#RunWith(SpringRunner.class) to your test, otherwise the annotations
will be ignored. If you are using JUnit 5, there’s no need to add the
equivalent #ExtendWith(SpringExtension.class) as #SpringBootTest and
the other #…Test annotations are already annotated with it.
Reference: https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing
If your test case is simple, you can refer to the example using #RestClientTest here.
In my case, I define some complicated configurations related to the tested service (Ex: jpaAuditingHandler, jpaMappingContext, ...). That's why I use #SpringBootTest to auto-configure.
This is my code sample:
#SpringBootTest
class MyTest {
#Autowired
private MyProperties myProperties;
#Autowired
private MyService myService;
private MockRestServiceServer server;
#Autowired
#Qualifier("restTemplateBean1")
private RestTemplate restTemplate;
#BeforeEach
void setUp() {
server = MockRestServiceServer.createServer(restTemplate);
}
#Test
void testCallRestServiceSuccess() throws Exception {
this.server.expect(requestTo(myProperties.getUrl())).andRespond(withStatus(HttpStatus.OK));
boolean result = myService.callRestService();
assertThat(result).isTrue();
}
}
Some other references:
MockRestResponseCreators javadoc: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/web/client/response/MockRestResponseCreators.html
https://www.baeldung.com/restclienttest-in-spring-boot
It looks like you have at least one too many Spring test annotations on your test.
What exactly do you want to test? If it is, indeed, just a RestClientTest, then this should work:
#RunWith(SpringRunner.class)
#TestPropertySource(locations = "classpath:test-application.properties")
#RestClientTest(YourRestClient.class)
public class MyIntegrationTest {
}
Or, go with just #SpringBootTest, but I don't know exactly what you want to do here.
I have the following test:
#SpringBootTest
#ExtendWith(SpringExtension.class)
class BookServiceImplTest {
#MockBean
private BookRepository bookRepository;
#MockBean
private LibraryService libraryService;
#Autowired
private BookServiceImpl bookService;
#Test
void create() {
BookRequestDTO bookRequestDTO = new BookRequestDTO();
Library library = new Library();
Book expectedBook = new Book();
when(libraryService.getById(bookRequestDTO.getLibraryId()))
.thenReturn(library);
when(bookRepository.save(any(Book.class)))
.thenReturn(expectedBook);
Book actualBook = bookService.create(bookRequestDTO);
assertEquals(expectedBook, actualBook);
}
}
It is okay, and it runs, but I was wondering is there a way to run this as a Unit test instead of a Integration tests and still use #MockBean #Autowired. Or am I missing some point?
I tried leaving only #ExtendWith(SpringExtension.class), but I get an Exception about BookServiceImpl bean not found.
I know how to do it using MockitoExtension and #Mock, #InjectMocks, but I was wondering if there is a more SpringBoot way of doing it?
You can make it unit test in four steps:
Remove #SpringBootTest annotation, as it is spinning up the entire Spring context ―not useful for unit tests where all collaborators are mocked
Remove #Autowired annotation from BookServiceImpl declaration and add a #BeforeEach setup method where you initialize bookService passing bookRepository and libraryService as parameters
Use MockitoExtension instead of SpringExtension in the ExtendWith annotation. Here I'm assuming you are able to use a library like Mockito for mocking your collaborators
Use Mockito's #Mock instead of #MockBean, as we are manually initializing bookService so there is no need to deal with Spring beans
To add a bit more on your first question: #Mockbean and #Autowired are annotations that make sense for integration tests as they handle the mocking and injection of beans. Unit tests should consider this class in isolation, mocking interactions with other classes, so there is no need to spin up the application context and set up beans.
Remove #SpringBootTest, this will load whole context and slow down your tests. The role of #MockBean is to create a mock from the specified bean and add it to the context. Since there's no context running, There's not point in using #MockBean
Annotate your unit test class with #RunWith(SpringRunner.class)
For injecting dependencies, this is a good practice to create a configuration file and mock beans explicitly and create the target bean using them. Assuming you're using constructor-based injection:
#Profile("test")
#Configuration
public class BookServiceTestConfiguration {
#Bean
public BookRepository bookRepository() {
return Mockito.mock(BookRepository.class);
}
#Bean
public LibraryService libraryService() {
return Mockito.mock(LibraryService.class);
}
#Bean
public BookService bookService() {
BookServiceImpl bookService = new BookServiceImpl(
bookRepository(), libraryService()
);
return userGroupService;
}
}
Then write your test class as:
#ActiveProfiles("test")
#Import(BookServiceTestConfiguration .class)
#RunWith(SpringRunner.class)
public class BookServiceUnitTest {
#Autowired
private BookService bookService;
// write unit tests
}
for further information, read this article
I am using springockito-annotations 1.0.9 for integration testing.
I have the following controller:
#Autowired
public Controller(
#Qualifier("passwordService ") PasswordService passwordService ,
#Qualifier("validator") Validator validator,
#Qualifier("reportService") ReportService reportService,
DateCalculator dateCalculator,
Accessor accessor){
this.passwordService = passwordService;
this.validator = validator;
this.reportService = reportService;
this.dateCalculator = dateCalculator;
this.accessor = accessor;
}
In the test I am going to replace beans from context using #ReplaceWithMock annotation.
But unfortunatly it works only for dependencies whithout #Qualifier annotation.
Namely, my test looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(loader = SpringockitoAnnotatedContextLoader.class, classes = {TestContext.class})
public class ControllerTest {
#Autowired
#ReplaceWithMock
private PasswordService passwordService ;
#Autowired
#ReplaceWithMock
private Validator validator;
#Autowired
#ReplaceWithMock
private ReportService reportService;
#Autowired
#ReplaceWithMock
private DateCalculator dateCalculator;
#Autowired
#ReplaceWithMock
private Accessor accessor;
#Autowired
private Controller controller;
}
In the last case after initializing context only DateCalculator and Accessor beans replacing correctly with needed mocks, but the another bean autowiring as normal beans from main context.
After debugging I have found that QualifierAnnotationAutowireCandidateResolver couldn't identify correctly bean. In the lines below beginning from 229:
RootBeanDefinition bd = (RootBeanDefinition) bdHolder.getBeanDefinition();
AutowireCandidateQualifier qualifier = bd.getQualifier(type.getName());
Spring tried to extract qualifier from mocked dependency, but it is empty.
Will be good to know how I can correctly replace dependency with #Qualifier to mock object.
Edit: added a link to alternatives to whitebox, it disappears in later versions of Mockito
It is possible to use mockitos #Mock and #InjectMocks to inject things into your class that you want to test, as suggested in an other post. I used to think it was a great way to test spring managed beans, but now i think it is problematic; if the
injection done by #InjectMocks fails, it does so silently, and you don't know why. When creating the test it can be manageble, but when you have some tests like this, and several tests starts to fail with nullpointers because of a small unintentional
change to the application context that someone made, or after a merge that introduced a minor anomaly, or similar, it gets more confusing than it need to be.
I advise you to use mockitos Whitebox instead, see my example below. With it you can explicitly tell what field you want to "inject" with what object, and in complicated situations, you can inject into more then one object. Mockito uses Whitebox when injecting (but swallows all exceptions).
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration( /*something that fits your setup*/ )
public class ControllerTest {
#Autowired
private PasswordService passwordService ;
#Autowired
private Validator validator;
#Autowired
private ReportService reportService;
#Autowired
private Controller testObject;
#Before
public void setupBefore() {
//Since this "injection" is done manually the qualifiers does not matter
Whitebox.setInternalState(testObject, "passwordService", passwordService);
Whitebox.setInternalState(testObject, "validator", validator);
Whitebox.setInternalState(testObject, "reportService", reportService);
}
#Test
public void testSomething() {
}
}
If you are doing regular unit testing Whitebox can help to do testing without a spring context. I can highly recommend that approach (but it is a bit off topic, and I will not post the example I wrote before I noticed that you were doing integration tests ;) ).
Edit: If you are using later versions of Mockito you will notice that Whitebox has disappeared, so what to do instead?
I faced that situation, and asked for advice: What do I use instead of Whitebox in Mockito 2.2 to set fields?
You don't need to do it any more. Mockito itself starting from version 1.8.3 now support annotated mocks and mock injections as described here: http://docs.mockito.googlecode.com/hg/1.9.5/org/mockito/Mockito.html#21
We do now the following for our unit tests:
// No annotation required
public class SomeTest {
#Mock
private SomeDependency someDependency;
#Mock
private SomeDependency2 someDependency2;
#InjectMocks
private ClassUnderTest classUnderTest;
#BeforeMethod(alwaysRun = true)
public void setUp() {
MockitoAnnotations.initMocks(this);
}
public void testSomething() {
// Do your Mockito test here.
}
}