Injecting Spring Boot component with Mockito mocks - java

Here is my GitHub repo for reproducing the exact issue.
Not sure if this is a Spring Boot question or a Mockito question.
I have the following Spring Boot #Component class:
#Component
class StartupListener implements ApplicationListener<ContextRefreshedEvent>, KernelConstants {
#Autowired
private Fizz fizz;
#Autowired
private Buzz buzz;
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// Do stuff involving 'fizz' and 'buzz'
}
}
So StartupListener has no constructor and is intentionally a Spring #Component that gets its properties injected via #Autowired.
The #Configuration class providing these dependencies is here, for good measure:
#Configuration
public class MyAppConfiguration {
#Bean
public Fizz fizz() {
return new Fizz("OF COURSE");
}
#Bean
public Buzz buzz() {
return new Buzz(1, true, Foo.Bar);
}
}
I am now trying to write a JUnit unit test for StartupListener, and I have been using Mockito with great success. I would like to create a mock Fizz and Buzz instance and inject StartupListener with them, but I'm not sure how:
public class StartupListenerTest {
private StartupListener startupListener;
#Mock
private Fizz fizz;
#Mock
price Buzz buzz;
#Test
public void on_startup_should_do_something() {
Mockito.when(fizz.calculateSomething()).thenReturn(43);
// Doesn't matter what I'm testing here, the point is I'd like 'fizz' and 'buzz' to be mockable mocks
// WITHOUT having to add setter methods to StartupListener and calling them from inside test code!
}
}
Any ideas as to how I can accomplish this?
Update
Please see my GitHub repo for reproducing this exact issue.

You can use #SpyBeaninstead of #MockBean, SpyBean wraps the real bean but allows you to verify method invocation and mock individual methods without affecting any other method of the real bean.
#SpyBean
private Fizz fizz;
#SpyBean
price Buzz buzz;

You can use #MockBean to mock beans in ApplicationContext
We can use the #MockBean to add mock objects to the Spring application context. The mock will replace any existing bean of the same type in the application context.
If no bean of the same type is defined, a new one will be added. This annotation is useful in integration tests where a particular bean – for example, an external service – needs to be mocked.
To use this annotation, we have to use SpringRunner to run the test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
public class MockBeanAnnotationIntegrationTest {
#MockBean
private Fizz fizz;
}
And i will also suggest to use #SpringBootTest
The #SpringBootTest annotation tells Spring Boot to go and look for a main configuration class (one with #SpringBootApplication for instance), and use that to start a Spring application context.

you can do something likewise,
#RunWith(MockitoJUnitRunner.class)
public class StartupListenerTest {
#Mock
private Fizz fizz;
#Mock
price Buzz buzz;
#InjectMocks
private StartupListener startupListener;
#Test
public void on_startup_should_do_something() {
Mockito.when(fizz.calculateSomething()).thenReturn(43);
....
}
}

Here is a simple example that just uses plain Spring.
package com.stackoverflow.q54318731;
import static org.junit.Assert.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
#SuppressWarnings("javadoc")
public class Answer {
/** The Constant SPRING_CLASS_RULE. */
#ClassRule
public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();
/** The spring method rule. */
#Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
static final AtomicInteger FIZZ_RESULT_HOLDER = new AtomicInteger(0);
static final int FIZZ_RESULT = 43;
static final AtomicInteger BUZZ_RESULT_HOLDER = new AtomicInteger(0);;
static final int BUZZ_RESULT = 42;
#Autowired
ConfigurableApplicationContext configurableApplicationContext;
#Test
public void test() throws InterruptedException {
this.configurableApplicationContext
.publishEvent(new ContextRefreshedEvent(this.configurableApplicationContext));
// wait for it
TimeUnit.MILLISECONDS.sleep(1);
assertEquals(FIZZ_RESULT, FIZZ_RESULT_HOLDER.get());
assertEquals(BUZZ_RESULT, BUZZ_RESULT_HOLDER.get());
}
#Configuration
#ComponentScan //so we can pick up the StartupListener
static class Config {
final Fizz fizz = Mockito.mock(Fizz.class);
final Buzz buzz = Mockito.mock(Buzz.class);
#Bean
Fizz fizz() {
Mockito.when(this.fizz.calculateSomething())
.thenReturn(FIZZ_RESULT);
return this.fizz;
}
#Bean
Buzz buzz() {
Mockito.when(this.buzz.calculateSomethingElse())
.thenReturn(BUZZ_RESULT);
return this.buzz;
}
}
#Component
static class StartupListener implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
private Fizz fizz;
#Autowired
private Buzz buzz;
#Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
FIZZ_RESULT_HOLDER.set(this.fizz.calculateSomething());
BUZZ_RESULT_HOLDER.set(this.buzz.calculateSomethingElse());
}
}
static class Fizz {
int calculateSomething() {
return 0;
}
}
static class Buzz {
int calculateSomethingElse() {
return 0;
}
}
}

If you modify your StartupListenerTest to just focus on the StartupListener class
i.e. add the class to the SpringBootTest annotation
#SpringBootTest(classes= {StartupListener.class})
You will get a different error, but it's more focused on the class you're trying to test.
onApplicationEvent method will fire before the test runs. This means you won't have initialized your mock with when(troubleshootingConfig.getMachine()).thenReturn(machine); and so there's no Machine returned when getMachine() is called, hence the NPE.
The best approach to fix this really depends on what you're trying to achieve from the test. I would use an application-test.properties file to set up the TroubleShootingConfig rather than use an #MockBean. If all you're doing in your onApplicationEvent is logging then you could use #SpyBean as suggested in another answer to this question. Here's how you could do it.
Add an application-test.properties to resources folder so it's on the classpath:
troubleshooting.maxChildRestarts=4
troubleshooting.machine.id=machine-id
troubleshooting.machine.key=machine-key
Add #Configuration to TroubleshootingConfig
#Configuration
#ConfigurationProperties(prefix = "troubleshooting")
public class TroubleshootingConfig {
private Machine machine;
private Integer maxChildRestarts;
... rest of the class
Change StartupListenerTest to focus on the classes your testing and spy on the TroubleshootingConfig. You also need to #EnableConfigurationProperties
#RunWith(SpringRunner.class)
#SpringBootTest(classes= {TroubleshootingConfig.class, StartupListener.class})
#EnableConfigurationProperties
public class StartupListenerTest {
#Autowired
private StartupListener startupListener;
#SpyBean
private TroubleshootingConfig troubleshootingConfig;
#MockBean
private Fizzbuzz fizzbuzz;
#Mock
private TroubleshootingConfig.Machine machine;
#Mock
private ContextRefreshedEvent event;
#Test
public void should_do_something() {
when(troubleshootingConfig.getMachine()).thenReturn(machine);
when(fizzbuzz.getFoobarId()).thenReturn(2L);
when(machine.getKey()).thenReturn("FLIM FLAM!");
// when
startupListener.onApplicationEvent(event);
// then
verify(machine).getKey();
}
}

Related

Running Spring JUnit tests without #InjectMocks

I am trying to test a class (and it’s methods) using a JUnit test without the #InjectMocks annotation. The annotation will not work for me because the class I am attempting to test has class attributes that #InjectMocks will not create.
Here is what my functional class looks like:
(File path: src/main/java/com/mycompany/mypackage)
package com.mycompany.mypackage;
#Service
public class MyClass {
#Value(“${someString}”)
private String someString;
public printString() {
System.out.println(someString);
}
}
Here is what my testing class looks like:
(File path: src/test/java/com/mycompany/mypackage)
package com.mycompany.mypackage;
#RunWith(SpringJUnit4ClassRunner.class)
public class MyClassTest {
#InjectMocks
MyClass myClass
#Test
public testPrintString() {
myClass.printString()
}
}
I’ve tried to use #Autowired in the place of #InjectMocks to fully instantiate the MyClass bean, but this will not actually create a instance of MyClass, and I will get a null pointer exception whenever I try to use the ‘myClass’ variable in a test.
For my particular use case, using a constructor method is not a realistic alternative.
Any help would be awesome!
Try adding this to your test class to set the value using reflection:
import org.junit.Before;
import org.springframework.test.util.ReflectionTestUtils;
#Before
public void setup()
{
ReflectionTestUtils.setField(
myClass,"someString",
"SOME_VALUE", String.class);
}
If you do not want mock test, you should do as below.
// #RunWith(SpringJUnit4ClassRunner.class)
#RunWith(SpringRunner.class)
#SpringBootTest(classes = YourApplication.class) // YourApplication is your spring boot app main class used to import spring context
public class MyClassTest {
// #InjectMocks
#Autowired
MyClass myClass;
#Test
public void testPrintString() {
myClass.printString();
}
}

How to Test void Method with spring repository using Junit and Mockito

i have a java spring service that call a spring data repository and i want to do a Junit test with mockito , this is my class and the service that i want to test :
#Service
public class DataServiceImpl implements DataService{
#Autowired
private CautionRepository cautionRepository;
#Override
public void addCautions(List<CautionsDTO> cautions, Contrat contrat) {
if(cautions != null && !cautions.isEmpty()) {
cautions.forEach(caution -> {
Caution caution = new Caution();
cautionContrat.setAmount(caution.getAmount());
cautionContrat.setDate(caution.getDate());
caution.setContrat(contrat);
cautionRepository.save(caution);
});
}
}
}
and this is my Unit test
#RunWith(SpringRunner.class)
public class DataServiceImplTest{
#InjectMocks
private DataServiceImpl dataService;
#Mock
private CautionRepository cautionRepository;
#Test
public void addListCautionsTest() {
List<CautionsDTO> cautions = new ArrayList<>();
ContratExportation contrat = new ContratExportation();
Caution caution = new Caution();
dataDelService.addCautions(cautions,contrat);
Mockito.verify(cautionRepository, times(1)).save(caution);
}
}
When i run the test i got the folowwing error :
Wanted but not invoked:
cautionRepository.save(
org.model.Caution#2abe9173
);
-> at org.service.DataServiceImplTest.addListCautionsTest(DataServiceImplTest.java:292)
Actually, there were zero interactions with this mock.
Do you have any idea please what is the mistake with my test
You never add a value to cautions so the loop is not executed and verify must fail.
Add a value to the list and the test should pass:
List<CautionsDTO> cautions = new ArrayList<>();
ContratExportation contrat = new ContratExportation();
CautionDTO caution = new CautionDTO();
cautions.add(caution);
dataDelService.addCautions(cautions,contrat);
This should be the correct code for executing the test
#RunWith(SpringRunner.class)
public class DataServiceImplTest{
#Autowired
private DataServiceImpl dataService;
#MockBean
private CautionRepository cautionRepository;
#Test
public void addListCautionsTest() {
List<CautionsDTO> cautions = new ArrayList<>();
ContratExportation contrat = new ContratExportation();
Caution caution = new Caution();
dataDelService.addCautions(cautions,contrat);
Mockito.verify(cautionRepository, times(1)).save(caution);
}
}
First of all, you forgot to add caution object into cautions list. But other than that you are mixing Unit test with Integration test.
You need to annotate your test class with #RunWith(MockitoJUnitRunner.class),
or
You need to annotate your mock objects with #MockBean and add #Autowired to your test class DataServiceImpl object.
Now, let me explain to you.
Unit Test
When you want to write a unit test, you should not make use of application context (autowiring).
By the way, a better approach is to annotate your DataServiceImpl with #RequiredArgsConstructor from Lombok and remove #Autowired from CautionRepository. This will allow you to instantiate DataServiceImpl in a setup method in your unit test.
Your DataServiceImpl class should be:
#Service
#RequiredArgsConstructor
public class DataServiceImpl implements DataService{
private final CautionRepository cautionRepository;
#Override
public void addCautions(List<CautionsDTO> cautions, Contrat contrat) {
// your code...
}
}
and your new unit test class:
#RunWith(MockitoJUnitRunner.class)
public class DataServiceImplTest{
private DataServiceImpl dataService;
#Mock
private CautionRepository cautionRepository;
#Before
public void setup() {
dataService = new DataServiceImpl(cautionsRepository);
}
#Test
public void addListCautionsTest() {
// your test code...
}
}
Integration Test
Now, if you want to create an integration test, use #RunWith(SpringRunner.class). By doing this your application context will be loaded. In your case you can create a mocked bean inside your context by annotating your object with #MockBean. This will inject mocked object into your context and it will get auto wired in your real class.
For this your new DataServiceImpl class can remain same as above. But change your integration test into:
#RunWith(SpringRunner.class)
public class DataServiceImplTest{
#Autowired
private DataServiceImpl dataService;
#MockBean // it will be injected automatically
private CautionRepository cautionRepository;
#Test
public void addListCautionsTest() {
// your test code...
}
}
Hope, you now understand the difference and the mistake you were doing :)

Mock embedded objects in integration test

Trying to write an integration test for a Spring application. Say i've got a class A which contains a class B object. Class B contains a class C object and I need to mock an object within this class for the integration test - any idea how i go about doing that without passing every object through as a parameter in the constructor?
e.g.
#Service
Class A {
#Autowired
private B b;
public void testA() {
B.testB();
}
}
#Service
Class B {
#Autowired
private C c;
public void testB() {
c.testC();
}
}
#Service
Class C {
//External class pulled in from dependency library
#Autowired
private RestTemplate restTemplate;
public void testC() {
restTemplate.doSomethingInOutsideWorld();
}
}
Integration test:
#RunWith(JUnitParamsRunner.class)
#SpringBootTest
public class MyIt {
#ClassRule
public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();
#Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
#Mock
private RestTemplate restTemplate;
#Autowired
private A a;
#InjectMocks
private C c;
#Before
public void setup() {
initMocks(this);
}
#Test
public void test1() throws IOException {
a.testA()
}
}
Doesn't mock the RestTemplate object, it tries to hit the outside world. Any advice on how to resolve this?
Achieve this by using SpringRunner and #MockBean
#RunWith(SpringRunner.class) is used to provide a bridge between Spring Boot test features and JUnit. Whenever we are using any Spring Boot testing features in out JUnit tests, this annotation will be required.
The #SpringBootTest annotation can be used when we need to bootstrap the entire container. The annotation works by creating the ApplicationContext that will be utilized in our tests.
Annotation that can be used to add mocks to a Spring ApplicationContext. Can be used as a class level annotation or on fields in either #Configuration classes, or test classes that are #RunWith the SpringRunner.
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyIt {
#MockBean
private RestTemplate restTemplate;
#Autowired
private A a;
#Before
public void setup() {
initMocks(this);
}
#Test
public void test1() throws IOException {
given(this.restTemplate.doSomethingInOutsideWorld()).willReturn(custom object);
a.testA()
}
}

Injecting mocks with Mockito does not work

I'm using Mockito to test my Spring project, but the #InjectMocks seems not working in injecting a mocked service into another Spring service(bean).
Here is my Spring service that I want to test:
#Service
public class CreateMailboxService {
#Autowired UserInfoService mUserInfoService; // this should be mocked
#Autowired LogicService mLogicService; // this should be autowired by Spring
public void createMailbox() {
// do mething
System.out.println("test 2: " + mUserInfoService.getData());
}
}
And below is the service that I want to mock:
#Service
public class UserInfoService {
public String getData() {
return "original text";
}
}
My test code is here:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/root-context.xml" })
public class CreateMailboxServiceMockTest {
#Mock
UserInfoService mUserInfoService;
#InjectMocks
#Autowired
CreateMailboxService mCreateMailboxService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void deleteWithPermission() {
when(mUserInfoService.getData()).thenReturn("mocked text");
System.out.println("test 1: " + mUserInfoService.getData());
mCreateMailboxService.createMailbox();
}
}
but the result would like
test 1: mocked text
test 2: original text // I want this be "mocked text", too
it seems that the CreateMailboxService didn't get the mocked UserInfoService but using Spring's autowired bean.
Why is my #InjectMocks not working?
In my case, I had a similar issue when I worked with JUnit5
#ExtendWith(MockitoExtension.class)
class MyServiceTest {
...
#InjectMocks
MyService underTest;
#Test
void myMethodTest() {
...
}
underTest was null.
The cause of the problem was that I used #Test from JUnit4 package import org.junit.Test; instead JUnit5 import org.junit.jupiter.api.Test;
For those who stumbles on this thread and are running with JUnit 5 you need to replace
#RunWith(SpringJUnit4ClassRunner.class)
with
#ExtendWith(MockitoExtension.class)
#RunWith(JUnitPlatform.class)
Further reading here. Unfortunately there is no hint when executing the test cases with JUnit 5 using the old annotation.
You can create package level setter for mUserInfoService in CreateMailboxService class.
#Service
public class CreateMailboxService {
#Autowired UserInfoService mUserInfoService; // this should be mocked
#Autowired LogicService mLogicService; // this should be autowired by Spring
public void createMailbox() {
// do mething
System.out.println("test 2: " + mUserInfoService.getData());
}
void setUserInfoService(UserInfoService mUserInfoService) {
this.mUserInfoService = mUserInfoService;
}
}
Then, you can inject that mock in the test using the setter.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/root-context.xml" })
public class CreateMailboxServiceMockTest {
#Mock
UserInfoService mUserInfoService;
CreateMailboxService mCreateMailboxService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mCreateMailboxService = new CreateMailboxService();
mCreateMailboxService.setUserInfoService(mUserInfoService);
}
...
}
This way you can avoid problems with #InjectMocks and Spring annotations.
If you are trying to use the #Mock annotation for a test that relies directly on Spring injection, you may need to replace #Mock with #MockBean #Inject (both annotations), and #InjectMocks with #Inject. Using your example:
#MockBean
#Inject
UserInfoService mUserInfoService;
#Inject
CreateMailboxService mCreateMailboxService;
I had a pretty similar situation. I am writing it down just in case any reader is going through the same. In my case I found that the problem was that I was setting my injected variable as final in the local class.
Following your example, I had things like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/root-context.xml" })
public class CreateMailboxServiceMockTest {
#Mock
UserInfoService mUserInfoService;
#InjectMocks
CreateMailboxService mCreateMailboxService = new CreateMailboxService(mUserInfoService);
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void deleteWithPermission() {
...
}
}
But in this class I had it like this:
#Service
public class CreateMailboxService {
private final UserInfoService mUserInfoService; // it is NOT injecting Mocks just because it is final! (all ok with private)
private final LogicService mLogicService; // it is NOT injecting Mocks just because it is final! (all ok with private)
#Autowired
public CreateMailboxService(UserInfoService mUserInfoService, LogicService mLogicService) {
this.mUserInfoService = mUserInfoService;
this.mLogicService = mLogicService;
}
public void createMailbox() {
...
}
}
Just deleting the final condition, #InjectMocks problem was solved.
For those who are running with JUnit 5 you need to replace the #RunWith(SpringJUnit4ClassRunner.class) with #ExtendWith(MockitoExtension.class).
For further reading take a look here.
there is no need of #Autowired annotation when you inject in the test class. And use the mock for the method to get your mocked response as the way you did for UserInfoService.That will be something like below.
Mockito.when(mCreateMailboxService. getData()).thenReturn("my response");
You can use MockitoJUnitRunner to mock in unit tests.
Use #Mock annotations over classes whose behavior you want to mock.
Use #InjectMocks over the class you are testing.
Its a bad practice to use new and initialize classes (better to go for dependency injection) or to introduce setters for your injections. Using setter injection to set dependencies only for tests is wrong as production code should never be altered for tests.
#RunWith(MockitoJUnitRunner.class)
public class CreateMailboxServiceMockTest {
#Mock
UserInfoService mUserInfoService;
#InjectMocks
CreateMailboxService mCreateMailboxService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
...
}

How to inject mock into #Service that has #Transactional

I have any issue in my unit test where I have something along the lines of this. The mock injection get overridden on the someService if the blargh function is annotated with Transactional. If I remove the Transactional the mock stays there. From watching the code it appears that Spring lazily loads the services when a function in the service is annotated with transactinal, but eagerly loads the services when it isn't. This overrides the mock I injected.
Is there a better way to do this?
#Component
public class SomeTests
{
#Autowired
private SomeService someService;
#Test
#Transactional
public void test(){
FooBar fooBarMock = mock(FooBar.class);
ReflectionTestUtils.setField(someService, "fooBar", fooBarMock);
}
}
#Service
public class someService
{
#Autowired FooBar foobar;
#Transactional // <-- this causes the mocked item to be overridden
public void blargh()
{
fooBar.doStuff();
}
}
Probably you could try to implement your test in the following way:
#Component
#RunWith(MockitoJUnitRunner.class)
public class SomeTests
{
#Mock private FooBar foobar;
#InjectMocks private final SomeService someService = new SomeService();
#Test
#Transactional
public void test(){
when(fooBar.doStuff()).then....;
someService.blargh() .....
}
}
I could not try it right now as don't have your config and related code. But this is one of the common way to test the service logic.
Use the Spring #Profile functionality - beans can be associated to a certain group, and the group can be activated or deactivated via annotations.
Check this blog post and the documentation for more detailed instructions, this is an example of how to define production services and two groups of mock services:
#Configuration
#Profile("production")
public static class ProductionConfig {
#Bean
public InvoiceService realInvoiceService() {
...
}
...
}
#Configuration
#Profile("testServices")
public static class TestConfiguration {
#Bean
public InvoiceService mockedInvoiceService() {
...
}
...
}
#Configuration
#Profile("otherTestServices")
public static class OtherTestConfiguration {
#Bean
public InvoiceService otherMockedInvoiceService() {
...
}
...
}
And this is how to use them in the tests:
#ActiveProfiles("testServices")
public class MyTest extends SpringContextTestCase {
#Autowired
private MyService mockedService;
// ...
}
#ActiveProfiles("otherTestServices")
public class MyOtherTest extends SpringContextTestCase {
#Autowired
private MyService myOtherMockedService;
// ...
}
I have the exact same problem and I solve it by using Mockito.any() for the arguments
eg:
when(transactionalService.validateProduct(id)).thenReturn("")
=> when(transactionalService.validateProduct(Mockito.any())).thenReturn("")

Categories

Resources