I'm implementing a service using Spring Boot and Spring Cloud Config service to provide the configuration values. In my Service I have a couple of config values which need to refresh when the value changes in the remote Git repo, and I was using #RefreshScope to enable that feature.
The problem comes when I try to inject a mock for RestTemplate in that service, it appears to ignore it and use the autowired instance instead. If I comment out the annotation it seems to work fine.
Here's the code for the Service:
#Service
#RefreshScope
public class MyServiceImpl implements MyService {
private static final Logger LOG = Logger.getLogger(MyServiceImpl.class);
#Autowired
public RestTemplate restTemplate;
#Value("${opts.default}")
private String default;
#Value("${opts.address}")
private String address;
#Value("${opts.separator}")
private String separator;
...
}
Test source code:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
public class ServiceTest {
#Mock
private RestTemplate restTemplate;
#Autowired
#InjectMocks
private MyServiceImpl service;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
public void testMethod() throws Exception {
when(restTemplate.postForObject(anyString(), any(), eq(ServiceResponse.class), anyMap())).thenReturn(getSuccessfulResponse());
ServiceResponse response = service.doYourStuff();
Assert.assertNotNull(response);
Assert.assertTrue(response.isSuccessful());
}
...
}
When adding the #RefreshScope the bean becomes a proxy instead of an actual raw implementation. Currently the RestTemplate is set on the proxy rather then the underlying instance. (If you debug you would see that your MyServiceImpl is actually more like an instance of MyServiceImpl$SpringCgLib#353234).
To fix you need to manually set the dependency using ReflectionTestUtils and AopTestUtils. The latter is to obtain the actual proxy.
Remove the #InjectMocks annotation and add the following to your setup method after the initialization of the mocks:
Object actualTarget = AopTestUtils.getUltimateTargetObject(service);
ReflectionTestUtils.setfield(actualTarget, "restTemplate", restTemplate);
For versions earlier as 4.2 the following might do the trick
Object actualTarget = (service instanceof Advised) ? ((Advised) service).getTargetSource().getTarget() : service;
The problem is that Mockito doesn't detect the proxy and just sets the field. The ReflectionTestUtils doesn't detect the proxy either hence the manual unwrapping. I actually stepped into this trap a couple of times before, which led me to create SPR-14050 this morning to have it embedded in the ReflectionTestUtils to easy the pain a little.
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.
Java 8, JUnit 4 and Spring Boot 2.3 here. I have a situation where I have a #Component-annotated Spring Boot class that gets #Autowired with all its dependencies (the beans are defined in a #Configuration-annotated config class):
#Configuration
public class SomeConfig {
#Bean
public List<Fizz> fizzes() {
Fizz fizz = new Fizz(/*complex logic here*/);
return new Collections.singletonList(fizz);
}
#Bean
public Buzz buzz() {
return new Buzz(/*complex logic here*/);
}
}
#Component
public class ThingDoerinator {
#Autowired
private Lizz<Fizz> fizzes;
#Autowired
private Buzz buzz;
public String doStuff() {
// uses fizz and buzz extensively inside here...
}
}
I can easily write a unit test to inject all of these dependencies as mocks:
public class ThingDoerinatorTest extends AbstractBaseTest {
#Mock
private List<Fizz> fizzes;
#Mock
private Buzz buzz;
#InjectMocks
private ThingDoerinator thingDoerinator;
#Test
public void should_do_all_the_stuff() {
// given
// TODO: specify fizzes/buzz mock behavior here
// when
String theThing = thingDoerinator.doStuff();
// then
// TODO: make some assertions, verify mock behavior, etc.
}
}
And 80% of the time that works for me. However I am now trying to write some unit tests that are more like integration tests, where instead of injected mocks, I want the beans to be instantiated like normal and get wired into the ThingDoerinator class like they would be in production:
public class ThingDoerinatorTest extends AbstractBaseTest {
#Mock
private List<Fizz> fizzes;
#Mock
private Buzz buzz;
#InjectMocks
private ThingDoerinator thingDoerinator;
#Test
public void should_do_all_the_stuff() {
// given
// how do I grab the same Fizz and Buzz beans
// that are defined in SomeConfig?
// when -- now instead of mocks, fizzes and buzz are actually being called
String theThing = thingDoerinator.doStuff();
// then
// TODO: make some assertions, verify mock behavior, etc.
}
}
How can I accomplish this?
You can use SpringBootTest.
#SpringBootTest(classes = {Fizz.class, Buzz.class, ThingDoerinator.class})
#RunWith(SpringRunner.class)
public class ThingDoerinatorTest {
#Autowired
private Fizz fizz;
#Autowired
private Buzz buzz;
#Autowired
private ThingDoerinator thingDoerinator;
}
You don't need to mock anything, just inject your class in your test and your configuration will provide the beans to your ThingDoerinator class and your test case will work as if you are calling the ThingDoerinator. doStuff() method from some controller or other service.
TL;DR
I think, you are confused with tests running whole spring context and unit test which just test the function/logic we wrote without running whole context. Unit tests are written to test the piece of function, if that function would behave as expected with given set of inputs.
Ideal unit test is that which doesn't require a mock, we just pass the input and it produces the output (pure function) but often in real application this is not the case, we may find ourselves in situation when we interact with some other object in our application. As we are not about to test that object interaction but concerned about our function, we mock that object behaviours.
It seems, you have no issue with unit test, so you could mock your List of bean in your test class ThingDoerinator and inject them in your test case and your test case worked fine.
Now, you want to do the same thing with #SpringBootTest, so I am taking a hypothetical example to demonstrate that you don't need to mock object now, as spring context will have them, when #springBootTest will load whole context to run your single test case.
Let's say I have a service FizzService and it has one method getFizzes()
#Service
public class FizzService {
private final List<Fizz> fizzes;
public FizzService(List<Fizz> fizzes) {
this.fizzes = fizzes;
}
public List<Fizz> getFizzes() {
return Collections.unmodifiableList(fizzes);
}
}
Using constructor injection here1
Now, my List<Fizzes would be created through a configuration (similar to your case) and been told to spring context to inject them as required.
#Profile("dev")
#Configuration
public class FizzConfig {
#Bean
public List<Fizz> allFizzes() {
return asList(Fizz.of("Batman"), Fizz.of("Aquaman"), Fizz.of("Wonder Woman"));
}
}
Ignore the #Profile annotation for now2
Now I would have spring boot test to check that if this service will give same list of fizzes which I provided to the context, when it will run in production
#ActiveProfiles("test")
#SpringBootTest
class FizzServiceTest {
#Autowired
private FizzService service;
#Test
void shouldGiveAllTheFizzesInContext() {
List<Fizz> fizzes = service.getFizzes();
assertThat(fizzes).isNotNull().isNotEmpty().hasSize(3);
assertThat(fizzes).extracting("name")
.contains("Wonder Woman", "Aquaman");
}
}
Now, when I ran the test, I saw it works, so how did it work, let's understand this. So when I run the test with #SpringBootTest annotation, it will run similar like it runs in production with embedded server.
So, my config class which I created earlier, will be scanned and then beans created there would be loaded into the context, also my service class annotated with #Service annotation, so that would be loaded as well and spring will identify that it needs List<Fizz> and then it will look into it's context that if it has that bean so it will find and inject it, because we are supplying it from our config class.
In my test, I am auto wiring the service class, so spring will inject the FizzService object it has and it would have my List<Fizz> as well (explained in previous para).
So, you don't have to do anything, if you have your config defined and those are being loaded and working in production, those should work in your spring boot test the same way unless you have some custom setup than default spring boot application.
Now, let's look at a case, when you may want to have different List<Fizz> in your test, in that case you will have to exclude the production config and include some test config.
In my example to do that I would create another FizzConfig in my test folder and annotate it with #TestConfiguration
#TestConfiguration
public class FizzConfig {
#Bean
public List<Fizz> allFizzes() {
return asList(Fizz.of("Flash"), Fizz.of("Mera"), Fizz.of("Superman"));
}
}
And I would change my test a little
#ActiveProfiles("test")
#SpringBootTest
#Import(FizzConfig.class)
class FizzServiceTest {
#Autowired
private FizzService service;
#Test
void shouldGiveAllTheFizzesInContext() {
List<Fizz> fizzes = service.getFizzes();
assertThat(fizzes).extracting("name")
.contains("Flash", "Superman");
}
}
Using different profile for tests, hence #ActiveProfile annotation, otherwise the default profile set is dev which will load the production FizzConfig class as it would scan all the packages ( 2Remember the #Profile annotation above, this will make sure that earlier config only runs in production/dev env ). Here is my application.yml to make it work.
spring:
profiles:
active: dev
---
spring:
profiles: test
Also, notice that I am importing my FizzConfiguration with #Import annotation here, you can also do same thing with #SpringBootTest(classes = FizzConfig.class).
So, you can see test case has different values than in production code.
Edit
As commented out, since you don't want test to connect to database in this test, you would have to disable auto configuration for spring data JPA, which spring boot by default does that.
3You can create a another configuration class like
#Configuration
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
public TestDataSourceConfiguration {}
And then include that with your #SpringBootTest, this will disable default database setup and then you would be left with last piece of puzzle which is any repository interaction you might be having in your class.
Because now, you don't have auto configuration in your test case, those repository are just interfaces, which you can easily mock inside your test class
#ActiveProfiles("test")
#SpringBootTest
#Import(FizzConfig.class, TestDataSourceConfiguration.class)
class FizzServiceTest {
#MockBean
SomeRepository repository;
#Autowired
private FizzService service;
#Test
void shouldGiveAllTheFizzesInContext() {
// Use Mockito to mock behaviour of repository
Mockito.when(repository.findById(any(), any()).thenReturn(Optional.of(new SomeObject));
List<Fizz> fizzes = service.getFizzes();
assertThat(fizzes).extracting("name")
.contains("Flash", "Superman");
}
}
1Constructor injection is advisable instead of `#Autowired` in production code, if your dependencies are really required for that class to work, so please avoid it if possible.
3Please note that you create such configuration only in test package or mark with some profile, do not create it in your production packages, otherwise it will disable database for your running code.
I want to create JUnkt test for this endpoint:
#Autowired
private JwtTokenProvider jwtTokenProvider;
#PostMapping("reset_token")
public ResponseEntity<?> resetToken(#Valid #RequestBody ResetPasswordTokenDTO resetPasswordTokenDTO, BindingResult bindResult) {
final String login = jwtTokenProvider.getUsername(resetPasswordTokenDTO.getResetPasswordToken());
}
Full code: Github
JUnit test:
#Test
public void resetTokenTest_NOT_FOUND() throws Exception {
when(usersService.findByResetPasswordToken(anyString())).thenReturn(Optional.empty());
mockMvc.perform(post("/users/reset_token")
.contentType(MediaType.APPLICATION_JSON)
.content(ResetPasswordTokenDTO))
.andExpect(status().isNotFound());
}
I get NPE at this line when I run the code:
final String login = jwtTokenProvider.getUsername(resetPasswordTokenDTO.getResetPasswordToken());
How I can mock jwtTokenProvider properly? As you can see I have a file with test data which I load but the token is not extracted. Do you know how I can fix this issue?
The most straightforward way is to use Mockito and create mock instances and pass it directly to your controller class using constructor injection.
However, if you do not wish to use constructor injection (I recommend you to use it though, as it is much more explicit) you need to define your beans in a separate test configuration class
#Profile("test")
#Configuration
public class TestConfiguration {
#Bean
public JwtTokenProvider mockJwtTokenProvider() {
return Mockito.mock(JwtTokenProvider.class);
}
}
Also, add the correct profile to your test class by #ActiveProfiles("test")
You can consider using a #MockBean directly in your test class to mock your JwtTokenProvider. #MockBean annotation is Spring-ish and is included in spring-boot-starter-test. The Spring Boot documentation summarizes it well:
Spring Boot includes a #MockBean annotation that can be used to define
a Mockito mock for a bean inside your ApplicationContext. You can use
the annotation to add new beans or replace a single existing bean
definition. The annotation can be used directly on test classes, on
fields within your test, or on #Configuration classes and fields. When
used on a field, the instance of the created mock is also injected.
Mock beans are automatically reset after each test method.
The #MockBean annotation will make Spring look for an existing single bean of type JwtTokenProvider in its application context. If it exists, the mock will replace that bean, and if it does not exist, it adds the new mock in the application context.
Your test class would look like this:
import org.springframework.boot.test.mock.mockito.MockBean;
#MockBean
#Qualifier("xxx") //If there is more than one bean of type JwtTokenProvider
private JwtTokenProvider jwtTokenProvider;
#Test
public void resetTokenTest_NOT_FOUND() throws Exception {
when(jwtTokenProvider.getUsername(anyString())).thenReturn(Optional.empty());
mockMvc.perform(post("/users/reset_token")
.contentType(MediaType.APPLICATION_JSON)
.content(ResetPasswordTokenDTO))
.andExpect(status().isNotFound());
}
You might also want to check this and this.
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.
}
}