Spring Boot 1.3 testing with mocking nested Bean - java

I am trying to write an integration test that tests class A that has nested dependency injection. So I ultimately I want to inject mock of class C.
I am aware of the annotation #MockBean that is available from Spring Boot 1.4 but my hard requirement is Spring Boot 1.3 with Java 7.
Could someone help me how I can mock nested dependency?
public class A {
#Autowired
private B b;
private void run() {
b.run();
}
}
public class B {
#Autowired
private C c;
private void run() {
c.run();
}
}
public class C {
private void run() {
//some action
}
}
public class Test {
#Autowired
private A a;
#Test
private void runTest() {
a.run();
}
}

Create a Mock bean of the same type in the Test context and mark it as Primary.

Since the #MockBean has been released as of the Spring version 1.4, the best alternative you might use is the Mockito library and its mocking features instead:
C c = Mockito.mock(C.class);
Prefer the constructor injection instead of the setters one. You can pass this mocked class through the constructor.
Alternatively, there is a possibility to use #Profile to distinguish a real bean from the mocked one. Read more at Injecting Mockito Mocks into Spring Beans.

Related

Mock a method call called by another object which is autowired and interface in another class

I wanted to mock something like as
public class B {
#autowired
protected SomeInterface c; // interface
public String methodToBeCalled(SomeClass a, SomeClass b) {
... some opertations;
c.methodIWantToMock(x, y);
}
}
so in usual cases, I would be creating a object of class B and then have initialized all other class as Mock, but since the c is a interface and autowired, I am unable to find a way to mock it yet, I looked into other posts but none of them was related to this case.
Is there a way I could achieve this in spring using Junit and Mockito only.
Let's pretend for a minute that you're not using Spring and you want to mock C.
it does not matter that C is an interface. Mockito is capable of mocking an interface -> it creates an implementation of that interface with "do-nothing" methods.
SomeInterface c = Mockito.mock(SomeInterface.class);
It does not matter that C is autowired. You can use set it using reflection.
B bObj = new B();
ReflectionTestUtils.setField(bObj, "c", cMock);
Now that you've got your mock set up and set as a dependency within B, you can mock its methods however you'd like.
when(cMock).methodIWantToMock(any(), any()).thenReturn("blah");
Now, let's consider that you're using Spring.
You should probably move to constructor injection:
#Autowired
public B(SomeInterface c) {
this.c = c;
}
Now for the pure unit testing approach, you don't need to use ReflectionTestUtils and you can just construct B with your mock.
If you are using Spring Boot, you can use #MockBean
#SpringBootTest
#RunWith(SpringRunner.class) // if Junit 4
// #ExtendWith(SpringExtension.class) if Junit 5
class BTest {
#MockBean
private SomeInterface someInterfaceMock;
}
I wrote unit test for your class B with JUnit4. This might help you.
public class BTest {
#Mock
SomeInterface c;
#InjectMocks
B b;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testMethodToBeCalled() throws Exception {
String result = b.methodToBeCalled(new SomeClassA(), new SomeClassB());
Assert.assertEquals("", result);
}
}

Mockito: How to mock spring special DI that the injected object doesn't have no-arg constructor

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");
}
}

SpyBean not being injected everywhere

I'm having difficulty getting a spy bean into my ApplicationContext. I have a bean called utilities of type Utilities:
#Component("utilities")
public class Utilities {
<snip>
/**
* Returns a random int. This is provided mostly for testing mock-ability
*
* #return a random integer
*/
public int getRandom() {
return (int) (Math.random() * Integer.MAX_VALUE);
}
}
And it's used from within a class indirectly referenced by my Spring Integration flow.
Then I have this Jupiter test:
#TestInstance(Lifecycle.PER_CLASS)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
#ExtendWith(SpringExtension.class)
#ContextConfiguration( classes = {
XmlLocations.class,
VisitorManager.class,
Utilities.class,
UnixTimeChannel.class
})
#WebMvcTest
//#TestExecutionListeners( { MockitoTestExecutionListener.class })
public class FullIntegrationTest {
#Autowired
private MockMvc mvc;
#SpyBean
private Utilities utilities;
private ClientAndServer mockServer;
private static final int MOCK_SERVER_PORT = 9089;
#BeforeAll
public void setUpBeforeClass() {
Mockito.when(utilities.getRandom()).thenReturn(Integer.MAX_VALUE);
mockServer = ClientAndServer.startClientAndServer(MOCK_SERVER_PORT);
RestAssuredMockMvc.mockMvc(mvc);
(new MockServerPingInit()).initializeExpectations(mockServer);
(new MockServerFullIntegrationInit()).initializeExpectations(mockServer);
}
#Test
public void t00200_IncomingMessage() {
RestAssuredMockMvc.given()
.queryParam("example", "example")
.when()
.request("POST", "/api/v1/incoming")
.then()
.statusCode(equalTo(200));
}
<snip>
But even though I create the spy bean and use a when/thenReturn on it it doesn't float off into my application context waiting to be called and return it's mocked random value.
I know that the method utilities.getRandom() is getting called because I can place a breakpoint on it and debug the test, and it hits the getRandom method, but when I try to add a spy bean as shown above and mock out the getRandom to return a fixed value for testing the breakpoints still hits and so I can tell the real method not the mock is being called.
I've tried putting the when/thenReturn inside the test as well in case it's too early but it doesn't help.
Clearly I'm doing something wrong, possibly conceptually wrong. Halp!
I attempted to recreate your problem with a minimal configuration:
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {Ctx.class})
public class XTest {
#SpyBean
private Random random1;
#Autowired private Supplier<Integer> intSupplier;
#Test
public void test() {
Mockito.when(random1.nextInt()).thenReturn(Integer.MAX_VALUE);
int i = intSupplier.get();
System.out.println("i=" + i);
}
#Configuration
public static class Ctx {
#Bean
static Random random1() {
return ThreadLocalRandom.current();
}
#Bean
static Supplier<Integer> intSupplier(Random random1) {
return random1::nextInt;
}
}
}
And as expected it prints
i=2147483647
So, there must be an issue with your runtime configuration... Could you share that? I’m guessing spring-integration is using another ApplicationContext. I know this isn't an answer and I will delete it if it doesn't help.
Okay, Thank you all for attempts to help. Without meaning to frustrate, posting configuration and flow won't help I think, because of what I've found below:
There was an exception on closer inspection:
org.springframework.expression.AccessException: Could not resolve bean reference against BeanFactory
The reference in question was to a method inside the utilities which I have used #SpyBean on:
<int:transformer
expression="#utilities.asMap('licence_id', headers[licenceId], 'message', 'Delivered: ' + headers[confirmedMessage], 'secured_session_id', headers[visitorSession].getSecureSessionId())" />
It's not a separate ApplicationContext but rather SpEL won't accept the spy bean because the reference has changed or similar.
So, I've left the utilities alone and retrofitted another bean internal to that to generate the numbers, and used SpyBean on that. Now Spring Integration/SpEL is happy again because the utilities bean it's working with is correct and the mocking happens internal to that bean and transparently to SpEL.
#Component
public class RandomSupplier implements Supplier<Double> {
#Override
public Double get() {
return Math.random();
}
}
public class FullIntegrationTest {
#Autowired
private MockMvc mvc;
#SpyBean
private RandomSupplier randomSupplier;
#Autowired // This is only necessary for the toy test below
private Utilities utilities;
#BeforeEach
public void setupAfterInit() {
Mockito.when(randomSupplier.get()).thenReturn(0.5);
}
#Test
public void t0() throws IOException {
System.out.println(utilities.getRandom());
}
...
Now Spring Integration/SpEL is happy again because the utilities bean it's working is correct and the mocking happens internal to that bean.
Three lessons: Don't spy beans directly referenced in SpEL inside a Spring Integration Flow; Read the logs; You can never have enough indirection :)

Spring boot: inject mock into Runner class

I have a spring boot application and some other components which application should interact with. However, in my unit testing I am using just application functionality and I would like to mock outer API calls. I am stuck as I can't find the way to mock case like this:
My start class with main method:
#ComponentScan("com.sample.application")
#SpringBootApplication
public class MyApp implements CommandLineRunner {
#Autowired
private OuterAPI outerAPI;
public static void main(String[] args) {
SpringApplication.run(AdRedirectorMain.class, args);
}
#Override
public void run(String... args) throws Exception {
outerAPI.createInstances();
}
...
}
And here is my test class example:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = MyApp.class)
public class MyAppTest {
// any tests
}
I am working with Spring Boot, JUnit, Mockito.
So, I am facing the problem - how could I avoid this method call createInstances() with Mockito, via reflection or in any other way.
Have a look at Mocking and spying beans in the Spring Boot documentation.
You can use #MockBean in your test class to replace an autowired bean with a Mockito mock instance.
You can use #MockBean http://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html
or you can define an interface that you OuterAPI implements then for your test you provide a dummy implementation that makes a dummy call instead of actual call to the outerAPI.createInstances();
Another option that you have is to have configuration class like this:
#Configuration
#Profile(value = {"yourtest-profile"})
public class TestConfiguration{
#Primary
#Bean
public OuterAPI outerAPI() {
return Mockito.mock(OuterAPI.class);
}
}
and put it under scr/test/java

NoSuchBeanDefinitionException for dependencies of mocked beans

I am attempting to use mocks in my integration test and am not having much luck. I am using Spring 3.1.1 and Mockito 1.9.0, and the situation is as follows:
#Component
public class ClassToTest {
#Resource
private Dependency dependency;
}
and
#Component
public class Dependency {
#Resource
private NestedDependency nestedDependency;
}
Now, I want to do an integration test of ClassToTest using Spring's JavaConfig. This is what I have attempted, and it doesn't work:
#Test
#ContextConfiguration
public class ClassToTestIntegrationTest {
#Resource
private ClassToTest classToTest;
#Resource
private Dependency mockDependency;
#Test
public void someTest() {
verify(mockDependency).doStuff();
// other Mockito magic...
}
#Configuration
static class Config {
#Bean
public ClassToTest classToTest() {
return new ClassToTest();
}
#Bean
public Dependency dependency() {
return Mockito.mock(Dependency.class);
}
}
}
I have simplified my setup to make the question easier to understand. In reality I have more dependencies and only want to mock some of them - the others are real, based on config imported from my prod #Configuration classes.
What ends up happening is I get a NoSuchBeanDefinitionException saying that there are no beans of type NestedDependency in the application context. I don't understand this - I thought Spring would receive Mockito's mocked instance of Dependency and not even look at autowiring it. Since this isn't working I end up having to mock my entire object graph - which completely defeats the point of mocking!
Thanks in advance for any help!
I had the same problem and I found another solution.
When Spring instantiate all your beans, it will check if it's a Mockito Mock and in this case, I return false for injection property. To use it, just inject it in a Spring context
Code below:
public class MockBeanFactory extends InstantiationAwareBeanPostProcessorAdapter {
private static final MockUtil mockUtil = new MockUtil();
public MockBeanFactory() {
super();
}
#Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return !mockUtil.isMock(bean);
}
}
What Mockito does when mocking classes is it creates a subclass using cglib having some fancy name like: Dependency$EnhancerByMockito (IIRC). As you probably know, subclasses inherit fields from their parent:
#Component
public class Dependency {
#Resource
private NestedDependency nestedDependency;
}
public class Dependency$EnhancerByMockito extends Dependency{
//...
}
This means Spring still sees the field in base class when presented with mock. What you can do:
Use interfaces, which will cause Mockito to employ dynamic proxies rather than CGLIB-generated classes
Mock NestedDependency - I know it will just cascade the problem one level further
Disable #Resource annotation scanning for tests

Categories

Resources