I have the following use case:
I have a Test class with 3 components, from which 2 of them are inject into the third; I am using JUnit and Mockito for testing
public class MyTestClass{
#Mock
SomeService someService;
#Mock
AnotherService anotherService;
#InjectMock
MainService mainService;
#BeforeMethod
public void init() {
initMocks(this);
}
#Test
public void test(){
when(someService.someMethod(any())).thenReturn(something);
when(anotherService.someMethod(any()).thenReturn(something);
mainService.someMainMerhod();
// ...other assert logic
}
}
And here I have the MainService Spring component which has injected the two other components
#Component
public class MainService{
#Autowired
private SomeService someService; //Why here I have null component
private AnotherService anotherService; // and here I have an initialized component ???
public MainService(AnotherService anotherService){
this.anotherService = anotherService;
}
// implementation
}
Question 1 : Why someService instance is null when I am using both constructor and #Autowired?
Question 2 : Why if I am using only the constructor without #Autowired and vice versa, everything works, since I do not load the Spring context... I have unit tests...
The Javadoc states:
"Mockito will try to inject mocks only either by constructor injection, setter injection, or property injection in order. If any of the strategy fail, then Mockito won’t report failure; i.e. you will have to provide dependencies yourself."
Hence it will fail silently.
Related
So in this version, repository is null (and metric is not null) :
#Mock
OperationRepository repository;
#Mock
Metric metric;
SQLStrategy sqlStrategy;
#BeforeEach
public void setup() {
MockitoAnnotations.initMocks(this);
sqlStrategy = new SQLStrategy(metric);
}
In this version, metric is null (and repository is not null) :
#Mock
OperationRepository repository;
#Mock
ReneMetric reneMetric;
#BeforeEach
public void setup() {
MockitoAnnotations.initMocks(this);
}
#InjectMocks
private SQLStrategy sqlStrategy = new SQLStrategy(reneMetric);
This is how they are defined in the class :
#Slf4j
#RequiredArgsConstructor
#Component("sqlDb")
public class SQLStrategy extends StorageStrategy {
#Autowired
private OperationRepository repository;
private final Metric metric;
I was wondering what could be doing this behavior?
I can understand the need for #InjectMocks for repository to not be null since its an #autowired But I really dont understand why metric become null when I use #InjectMocks.
First of all , #InjectMocks is a Mockito stuff and is nothing to do with Spring. It has no ideas about #Autowired and its existence will not affect #InjectMocks 's behaviour.
Normally you do not need to manually create the instance that you want to inject mocks into. If that instance has non no-arg constructor , Mockito will choose biggest constructors to create instance and inject the mocks through this constructor.
But in case the instance has a no-arg constructor or you instantiate it manually, Mockito will first use property setter injection , and then field injection to inject mocks.
And one caveat is that for the final field , field injection will not happen . You can refer to the docs for more information.
So change to the following should inject both mocks into SQLStrategy :
#ExtendWith(MockitoExtension.class)
public class SQLStrategyTest {
#Mock
OperationRepository repository;
#Mock
ReneMetric reneMetric;
#InjectMocks
private SQLStrategy sqlStrategy;
#Test
public void test(){
}
}
#Slf4j
#RequiredArgsConstructor
#Component("sqlDb")
public class SQLStrategy extends StorageStrategy {
public SQLStrategy(OperationRepository repo , Metric metric){
this.repo = repo;
this.metric = metric;
}
}
And the following explains why your example get such result :
You manually instantiate SQLStrategy . But at the moment when it is instantiated , Mockito still not create a ReneMetric mock , so new SQLStrategy(null) is called to create SQLStrategy
Mockito create a mocked instance of OperationRepository and ReneMetric
Since you manually create SQLStrategy , Mockito will try to use property setter injection , and then field injection to inject the mocks into it. For the OperationRepository , it is injected because of the field injection. For the Metric , it is not injected because it is a final field and SQLStrategy does not have any setter for it.
So OperationRepository is not null and Metric is null
I'm using Mockito 3.4.6 in unit test, actually, i have integrated Mockito to my unit test and it works well. While, now i need to optimize some unit test, it's a special dependency injection that the injected object doesn't have no-arg constructor, I tried #Spy but it didn't work.
My Test: I tried 1. #Spy; 2. #Spy with setting instance using = getDtInsightApi(); 3. #Spy with #InjectMocks, all of tests are failed. As Mockito docs said, seems it can't work for this case.
#InjectMocks Mockito will try to inject mocks only either by constructor injection,
setter injection, or property injection in order and as described below.
Also if only use #Spy, it will throw MockitoException:
org.mockito.exceptions.base.MockitoException:
Failed to release mocks
This should not happen unless you are using a third-part mock maker
...
Caused by: org.mockito.exceptions.base.MockitoException: Unable to initialize #Spy annotated field 'api'.
Please ensure that the type 'DtInsightApi' has a no-arg constructor.
...
Caused by: org.mockito.exceptions.base.MockitoException: Please ensure that the type 'DtInsightApi' has a no-arg constructor.
See my pseudocode as below:
configure class:
#Configuration
public class SdkConfig {
#Resource
private EnvironmentContext environmentContext;
#Bean(name = "api")
public DtInsightApi getApi() {
DtInsightApi.ApiBuilder builder = new DtInsightApi.ApiBuilder()
.setServerUrls("sdkUrls")
return builder.buildApi();
}
}
DtInsightApi class with no public no-arg constructor and get instance by its inner class
public class DtInsightApi {
private String[] serverUrls;
DtInsightApi(String[] serverUrls) {
this.serverUrls = serverUrls;
}
// inner class
public static class ApiBuilder {
String[] serverUrls;
public ApiBuilder() {
}
...code...
public DtInsightApi buildApi() {
return new DtInsightApi(this.serverUrls);
}
}
...code...
}
unit test class:
public Test{
#Autowired
private PendingTestService service;
#Spy
private Api api = getDtInsightApi();
#Mock
private MockService mockService;
#Before
public void setUp() throws Exception {
// open mock
MockitoAnnotations.openMocks(this);
// i use doReturn(...).when() for #Spy object
Mockito.doReturn(mockService).when(api)
.getSlbApiClient(MockService.class);
Mockito.when(mockService.addOrUpdate(any(MockDTO.class)))
.thenReturn(BaseObject.getApiResponseWithSuccess());
}
public DtInsightApi getDtInsightApi () {
return new DtInsightApi.ApiBuilder()
.setServerUrls(new String[]{"localhost:8080"})
.buildApi();
}
#Test
public void testUpdate() {
service.update();
}
}
PendingTestService:
#Service
public class PendingTestService{
#Autowired
DtInsightApi api;
public void update() {
// here mockService isn't the object i mocked
MockService mockService = api.getSlbApiClient(MockService.class);
mockService.update();
}
}
Question: How to mock the DI object DtInsightApi which doesn't have no-arg constructor.
After checked Spring docs about unit test, I found a solution using #MockBean.
Spirng docs:https://docs.spring.io/spring-boot/docs/1.5.2.RELEASE/reference/html/boot-features-testing.html
According to Spring docs, you can use #MockBean to mock a bean inside your ApplicationContext, so i can use #MockBean to mock DtInsightApi.
It’s sometimes necessary to mock certain components within your application context when running tests. For example,
you may have a facade over some remote service that’s unavailable during development. Mocking can also be useful when
you want to simulate failures that might be hard to trigger in a real environment.
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 will also be injected.
Mock beans are automatically reset after each test method.
My Solution: Use #MockBean and BDDMockito.given(...).willReturn(...), use
#Qualifier("api") to specify the bean name because #MockBean injected by class type, if you have same class beans, you need to specify bean name.
My code in test class:
public class Test{
#MockBean
#Qualifier("api")
private DtInsightApi api;
#Mock
private MockService mockService;
#Before
public void setUp() throws Exception {
// open mock
MockitoAnnotations.openMocks(this);
BDDMockito.given(this.api.getSlbApiClient(MockService.class)).willReturn(mockService);
}
#Autowired
private PendingTestService service;
#Test
public void testUpdate() {
service.update();
}
}
Debug the mockService you can see mockService instance is generated by Mockito, mock succeed.
You can also refer to Spring docs example: mock RemoteService in unit test.
import org.junit.*;
import org.junit.runner.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.boot.test.context.*;
import org.springframework.boot.test.mock.mockito.*;
import org.springframework.test.context.junit4.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.BDDMockito.*;
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyTests {
#MockBean
private RemoteService remoteService;
#Autowired
private Reverser reverser;
#Test
public void exampleTest() {
// RemoteService has been injected into the reverser bean
given(this.remoteService.someCall()).willReturn("mock");
String reverse = reverser.reverseSomeCall();
assertThat(reverse).isEqualTo("kcom");
}
}
I am beginner with Mockito framework, and i have some problems in determining the mocked and injected mocks objects, Actually I have the following structure in my project.
//WebService Interface
Interface WebService{
#Gateway(...)
public x call1(parameters);
}
//Class that implements another interface
Class A implements interfaceA{
#Autowired
WebService WS;
public void M1(){
.....
WS.call1(parameters);
.....
}
}
//Test Class
#Mock
#Autowired
WebService WS;
#InjectMock
#Autowired
A a;
#Before
setup(){
MockitoAnnotations.initMocks(this);
}
#Test
#Rollback(true)
#Transactional
public void Test() {
when(WS.call1(parameters)).thenReturn(x);
actualResult = a.M1();
assertNotNull(actualResult);
verify(WS, Mockito.times(1)).call1(parameters);
}
Are the Mocked and injected mock objects chosen correctly?
And if yes, i keep getting this exception message:
Wanted but not invoked:
WS.call1(
........
);
Actually, there were zero interactions with this mock.
You are using #Mock and #Autowired together. This doesn't make sense. You either mock or autowire your beans. Remove Autowiring
#Mock
WebService WS;
#InjectMock
A a;
I am trying to set up my class to be used in Junit.
However when I try to do the below I get an error.
Current Test Class:
public class PersonServiceTest {
#Autowired
#InjectMocks
PersonService personService;
#Before
public void setUp() throws Exception
{
MockitoAnnotations.initMocks(this);
assertThat(PersonService, notNullValue());
}
//tests
Error:
org.mockito.exceptions.base.MockitoException:
Cannot instantiate #InjectMocks field named 'personService'
You haven't provided the instance at field declaration so I tried to construct the instance.
However the constructor or the initialization block threw an exception : null
How can I fix this?
You are not mocking anything in your code. #InjectMocks sets a class where a mock will be injected.
Your code should look like this
public class PersonServiceTest {
#InjectMocks
PersonService personService;
#Mock
MockedClass myMock;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
Mockito.doReturn("Whatever you want returned").when(myMock).mockMethod;
}
#Test()
public void testPerson() {
assertThat(personService.method, "what you expect");
}
Another solution is to use #ContextConfiguration annotation with static inner configuration class like so:
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class)
public class PersonServiceTest {
#Autowired
PersonService personService;
#Before
public void setUp() throws Exception {
when(personService.mockedMethod()).thenReturn("something to return");
}
#Test
public void testPerson() {
assertThat(personService.method(), "what you expect");
}
#Configuration
static class ContextConfiguration {
#Bean
public PersonService personService() {
return mock(PersonService.class);
}
}
}
Anyway, you need to mock something that the method you want to test uses inside to get desired behaviour of that method. It doesn't make sense to mock the service you're testing.
You're misunderstanding the purpose of the mock here.
When you mock out a class like this, you are pretending as if it's been injected into your application. That means you don't want to inject it!
The solution to this: set up whatever bean you were intending to inject as #Mock, and inject them into your test class via #InjectMocks.
It's unclear where the bean you want to inject is since all you have is the service defined, but...
#RunWith(MockitoJUnitRunner.class);
public class PersonServiceTest {
#Mock
private ExternalService externalSvc;
#InjectMocks
PersonService testObj;
}
If I am not mistaken...the thumb rule is you cannot use both together..you either run unit test cases with using MockitojunitRunner or SpringJUnitRunner you cannot use both of them together.
There have been many questions regarding request scoped management in unit tests and mainly answer is to do not test the scope management, as its a Spring Framework task and it should take care that it works properly. So advice, for example, would be to replace the request scope with thread or prototype type scope in the XML configuration file.
For most of the tests its enough, there are no complaints about not registered "request" scope and tests are running fine. But I do have one case where it is not enough.
Consider following case:
#Component
#Scope("request")
public class MyService {
#Autowired
private MyComponent component;
public void doSomething(String param) {
component.doTheThing(param);
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"classpath:my-scope-tweaks.xml"})
public class MyServiceTest {
#Autowired
private MyService service;
#Autowired
private MyComponent component;
#Test
public void test1() {
service.doSomething("aaa");
assertEquals("AAA", component.getTheThing());
}
#Test
public void test1() {
service.doSomething("bbb");
assertEquals("BBB", component.getTheThing());
}
}
I want to test MyService, which is request-scoped. MyComponent is request scope as well.
Variant A
If I replace the request scope with SimpleThreadScope, then in by both tests I would receive the same instance of MyService and MyComponent, so for example test2() could receive bad results from MyComponent as it could internally contain some internal "trash" from previous test1()
Variant B
If I replace request scope with prototype scope - I would get the case where my test methods are receiving different instances of MyComponent as MyService does - so I cannot perform any assertions on them.
So what I would need is kind of test method-related request scope, where all request-scoped beans remain just during the test1() method and then gets destroyed, so within next test2() they would be created newly again.
Is it possible?
This may not be what you're looking for, but why are you using Spring to manage your test classes in the first place? That seems like overkill to me. For unit testing, you shouldn't need a DI container. Just mock the dependencies and focus on the encapsulated functionality of the Component or Service you're testing.
You won't be able to do that, though, while using field injection. You'll need to convert to constructor or method injection.
It is possible to receive a new context for every method in your unit test by annotating the method with #DirtiesContext. This allows you to manipulate beans in the application context for one unit test and not have it affect the others.
Documentation
Your example annotated with #DirtiesContext:
#Component
#Scope("request")
public class MyService {
#Autowired
private MyComponent component;
public void doSomething(String param) {
component.doTheThing(param);
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"classpath:my-scope-tweaks.xml"})
public class MyServiceTest {
#Autowired
private MyService service;
#Autowired
private MyComponent component;
#Test
#DirtiesContext
public void test1() {
service.doSomething("aaa");
assertEquals("AAA", component.getTheThing());
}
#Test
#DirtiesContext
public void test2() {
service.doSomething("bbb");
assertEquals("BBB", component.getTheThing());
}
}
If all of your test methods within your class require a new context you can also just annotate the class as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"classpath:my-scope-tweaks.xml"})
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class MyServiceTest {
//Tests Here...
}