How to inject mock bean in SpringConfiguration to create another bean.? - spring

I have problem with unit test. Below is the sample code snippet. I have mock a bean and inject into #configuration class and use the mocked property to create another bean.
In the below, if i inspect, b.getSomething() returning me the default value like "" for string, 0 for int. etc. I am not getting the mocked value. Any idea how to do?
#Configuration
class A{
#Autowired B b;
#Bean
public SomeClass someBean(){
SomeClass clas = new SomeClass();
clas.setSomething(b.getSomething());
return clas;
}
}
#ContextConfiguration(classes = { A.class}, loader = SpringockitoAnnotatedContextLoader.class)
class ATest{
#ReplaceWithMock
#Autowired
B b;
#Before
public void setup(){
Mockito.when(b.getSomething()).thenReturn("ABC");
}
}

This is the way I create my mocks. Have a Bean which returns the Mock, and autowire it where needed.
#Autowired
MyClass myClassMock;
#Bean
public MyClass getMyClassMock(){
MyClass mock = Mockito.mock(MyClass.class);
Mockito.when(mock.getSomething()).thenReturn("ABC");
return mock;
}

Related

Is a mocked bean (#MockBean) mocked before the Spring Context loads the actual Spring Bean?

Let's use the following as an example.
#Autowired
#MockBean
private Foo foobar;
Does the Spring Context load the class Foo first, and then apply the mock? Or does the #Mockbean get detected somehow, and Spring creates and applies the mock instead of loading the class Foo into the Spring Context. I have a suspicion that it is the latter, but I would like confirmation.
Spring will throw an exception.
Let's define the class Foo.
#Component
public class Foo {
public Foo() {
System.out.println("I am not a mock");
}
}
Whenever testing using #Autowired, spring injects an instance of Foo and the constructor will print "I am not a mock", as in the code below.
#SpringBootTest(classes = Main.class)
#RunWith(SpringRunner.class)
public class FooTest {
#Autowired
Foo foo;
#Test
public void test() {
System.out.println(foo);
}
}
On the other hand, using #MockBean, spring will not create the real bean and the message in the constructor will not be printed. This scenario is represented by the following code.
#SpringBootTest(classes = Main.class)
#RunWith(SpringRunner.class)
public class FooTest {
#MockBean
Foo foo;
#Test
public void test() {
System.out.println(foo);
}
}
However, when you try to use both annotations together, spring will throw a BeanCreationException caused by an IllegalStateException. It means that the field foo cannot have an existing value. This scenario will happen in executing the code below:
#SpringBootTest(classes = Main.class)
#RunWith(SpringRunner.class)
public class FooTest {
// this will not work
#Autowired
#MockBean
Foo foo;
#Test
public void test() {
System.out.println(foo);
}
}
And the stack trace will be something like:
org.springframework.beans.factory.BeanCreationException: Could not inject field: com.tbp.Foo com.FooTest.foo; nested exception is java.lang.IllegalStateException: The field com.tbp.Foo com.FooTest.foo cannot have an existing value
at org.springframework.boot.test.mock.mockito.MockitoPostProcessor.inject(MockitoPostProcessor.java:413) ~[spring-boot-test-1.5.2.RELEASE.jar:1.5.2.RELEASE]

mock beans inside a method and those beans are also autowired in the same class or super class

#RunWith(SpringRunner.class)
#WebAppConfiguration
Class Test{
#Autowired
public SomeBean someBean;
#Test
public void testAccountPage2() throws Exception {
SomeBean someBean = mock(SomeBean.class);
given(someBean.getAccount(anyString())).willReturn(getCustomer());
}
Here someBean.getAccount(anyString()) is not mocking, which is calling the actual method of that bean impl. It seems it's taking the Autowired object not the mocked one.
Could anyone help me to mock beans in method level? those beans are also autowired in the same class or superclass.
Thanks
To replace a bean in the Spring container by a Mockito mock, use #MockBean.
import org.springframework.boot.test.mock.mockito.MockBean; // import to add
#RunWith(SpringRunner.class)
#WebAppConfiguration
Class Test{
#MockBean
public SomeBean someBean;
#Test
public void testAccountPage2() throws Exception {
given(someBean.getAccount(anyString())).willReturn(getCustomer());
}
}
To understand the difference between Mockito and MockBean from Spring Boot, you can refer to this question.
You need to inject the mock in order to have it working instead of autowiring
//if you are just testing bean/services normally you do not need the whole application context
#RunWith(MockitoJUnitRunner.class)
public class UnitTestExample {
#InjectMocks
private SomeBean someBean = new SomeBean();
#Test
public void sampleTest() throws Exception {
//GIVEN
given(
someBean.getAccount(
//you should add the proper expected parameter
any()
)).willReturn(
//you should add the proper answer, let's assume it is an Account class
new Customer()
);
//DO
//TODO invoke the service/method that use the getAccount of SomeBean
Account result = someBean.getAccount("");
//VERIFY
assertThat(result).isNotNull();
//...add your meaningful checks
}
}

#SpringBootTest not creating inner beans while loading the context

I would like to learn why inner beans are not created while trying to test like below :
RunWith(SpringRunner.class)
#SpringBootTest(classes=MyTest.class)
public class MyTest {
#SpyBean A a;
#Test
public void myTest() {
assertTrue(a.some());
}
#Component
class A {
private B b;
A(B dependency) {
this.b = dependency;
}
boolean some() {
return b.value();
}
}
#Configuration
class B {
boolean value() { return true; }
}
}
Error: No qualifying bean of type 'com.example.MyTest$B' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations:
Despite annotating the inner class with #Configuration it is not creating the bean while testing the method.
please note it works when I add like below #SpringBootTest(classes=MyTest.class,MyTest.B.class,MyTest.A.class})
Add #ContextConfiguration(classes = MyTest.B.class) to the MyTest class.
But
putting configuration into a test class isn't the best idea. It's better to create separate configuration class MyTestConfig that create all need beans for test and use it in the test class by #ContextConfiguration(classes = MyTestConfig.class).
Use #MockBean. And add behavior in #Test:
#SpringBootTest
#AutoConfigureMockMvc
#RunWith(SpringRunner.class)
public abstract class IntegrationTest {
#MockBean
A a;
#Test
public void mySuperTest(){
Mockito.when(a.getById(Mockito.any())).thenReturn(someInstance);
Assert.assertEquals(a.getById("id"), someInstance);
}
}

Spring Configuration - Inject Mock Beans

I am using Spring, Junit and Mockito. I need to override beans defined in the main spring configuration using another mockito test configuration (injecting mock beans only as needed). Nested beans have been #Autowired in the application.
Update:
Based on alfcope's answer below, it is important to add the name attribute so that spring can allow the primary bean (mock) to override the original one. Otherwise you get this exception:
org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);
The info message in the spring log shows:
Skipping bean definition for [BeanMethod:name=bar,declaringClass=test.package.MockitoTestConfiguration]: a definition for bean 'bar' already exists. This top-level bean definition is considered as an override.
Example:
I have a simplified example below that works. Here, Bar is the nested inside Foo, and I need to mock Bar for testing:
#Component
public class Foo
{
#Autowired
private Bar bar;
public String getResponseFromBar(String request)
{
String response = bar.someMethod(String request);
//do something else with this reponse
return response;
}
}
#Component
public class Bar {
public String someMethod(String request) {
String response = null;
//do something
return response;
}
}
Now for testing, let's say I want to inject a mockbar instead of the real bar. How can I achieve this in my test class below?
#Profile("test")
#Configuration
public class MockitoTestConfiguration {
//adding the name attribute is important.
#Bean(name="mockBar")
#Primary
public Bar bar() {
logger.debug("injecting mock bar");
return Mockito.mock(Bar.class);
}
}
Actual test case:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath*:test-context.xml")
public class FooTest {
#Autowired
Foo foo;
#Autowired
Bar mockBar; //need this to set up the mock response in the test case.
#Test
public void testAMethodInFoo_WithBarInjectedByMockito() {
//set up the mockBar response
Mockito.when(mockBar.someMethod("1")).thenReturn("1-response");
String response = foo.getResponseFromBar();
assertEquals("1-response", response);
}
}
Based on the ConfigurationClassBeanDefinitionReader code I guess you are using xml configuration to define your main bean. If so, just add a name when creating your mockito bean.
#Bean(name="mockbar")
#Primary
public Bar bar() {
logger.debug("injecting mock bar");
return Mockito.mock(Bar.class);
}
This way Spring will allow you to have both beans, and as you are using #Primary it will be the one used by your tests.
Spring overriding primary bean with non-primary bean
ConfigurationClassBeanDefinitionReader Code
Alternatively, if you use Mockito, you can do this and completely do away with the extra MockitoTestConfiguration class and named primary mock beans in the test profile. Just simply do this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath*:test-context.xml")
public class FooTest {
#Autowired
#InjectMocks
Foo foo;
#Mock
Bar mockBar; //need this to set up the mock response in the test case.
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
#Test
public void testAMethodInFoo_WithBarInjectedByMockito() {
//set up the mockBar response
Mockito.when(mockBar.someMethod("1")).thenReturn("1-response");
String response = foo.getResponseFromBar();
assertEquals("1-response", response);
}
}

Passing parameters to #Configuration in Spring

I have a requirement of creating a prototype bean that's stateful, i.e. take parameters in constructor.
I tried to use #Configuration to create that bean, but found it doesn't work if I use a parameterized constructor...
Note that the parameters I want to pass are NOT spring beans...they are simple POJOs...so I can't autowire them.
So this is what I want to do -
#Configuration
public class MyClassFactory {
#Bean
public MyClass getMyClass(Pojo1 pojo1, Pojo2 pojo2) {
return new MyClass (pojo1, pojo2);
}
}
#Scope("PROTOTYPE")
public class MyClass {
public MyClass(Pojo1 pojo1, Pojo2 pojo2) {
...
}
#Autowired SomeService1 service1;
#Autowired SomeService1 service2;
...
}
Of course I can make MyClass applicationContextAware, and pick up services from it, rather than making it a prototype bean...but was wondering why above pattern is not allowed...

Resources