Stubbing methods of injected beans in another bean's constructors - java

I test a Spring bean using constructor injection. The injected bean is defined as #MockBean in the testcase and also the appropriate stub is defined: when a certain method of the mocked bean is called, then it should return a mocked object.
When I start the testcase I get a NullPointerException, because the stubbing does not work – the method returns always null.
Here is the constructor of the object to test:
#Autowired
public MyBeanToTest(msTemplate jmsTemplate) throws JMSException {
this.jmsTemplate = jmsTemplate;
this.cf = this.jmsTemplate.getConnectionFactory(); // cf is always null
cf.createSession(); // NPE
}
Here is the testcase:
#SpringBootTest
public class MyTestClass {
#Autowired
MyBeanToTest myBeanToTest;
#MockBean
private JmsTemplate jmsTemplate;
#Mock
private static ConnectionFactory connectionFactory;
#Test
public void testSomething() {
...
when( jmsTemplate.getConnectionFactory() ).thenReturn( connectionFactory );
...
}
}
I assume the defined stubbing is not active yet when the constructor is called.
Any idea how can I make it work?

Your assumption is correct. Your best bet is to create the bean ‘by hand’ instead of letting Spring call the constructor all by itself. To this end, add a #Configuration class to your test that has the corresponping #Bean method.
Untested example:
#SpringBootTest
public class MyTestClass {
#Configuration
static class MyTestClassConfiguration {
#Bean
MyBeanToTest myBeanToTest(jmsTemplate jmsTemplate) {
// Too bad that this is not within the test…
when(jmsTemplate.getConnectionFactory())
.thenReturn(connectionFactory);
…
}
}
}

You would have to use static stub so it would exist and be configured prior creation of the context.
Another, cleaner approach, is to NOT to call anything in the constructor, but to create your session with cf.createSession(); on demand (it still can be singleton).
Yet another approach is to NOT to use #MockBean at all, and just create GegugProcessingSoapServices yourself.

I would suggest not to mock but to stub the JmsTemplate bean.
#SpringBootTest
public class MyTestClass {
#Autowired
MyBeanToTest myBeanToTest;
#Autowired
private JmsTemplate jmsTemplate;
#Mock
private static ConnectionFactory connectionFactory;
#TestConfiguration
static class Config {
#Bean
public JmsTemplate jmsTemplate() {
return new JmsTemplate() {
public ConnectionFactory getConnectionFactory() {
return connectionFactory;
}
}
}
}
#Test
public void testSomething() {
...
}
}

#AutoWired is not intended for use for class under test. It won't take the bean into the constructor .
The solution can be possible to use Mockito #InjectMock, or to create the class yourself and use a mock in the constructor (this is generally a good practice):
MyBeanToTest myBeanToTest = new MyBeanToTest(connectionFactory)

Related

Why tested class based autowire annotation throw null exception?

I use Spring Boot 5 and JUnit in my project. I create a unit test to test the service.
Here is the service that I am testing:
#Service
#RequiredArgsConstructor
#Slf4j
public class BuilderServiceImpl implements BuilderService{
#Autowired
public AutoMapper autoMapper;
private final BuilderRepository builderRepository;
private final AdminUserRepository adminUserRepository;
#Override
public BuilderDto getByEmail(String email){
}
#Override
public List<BuilderMinDto> getAll() {}
#Override
public List<BuilderMinDto> getAll(int page, int size) {}
#Override
public SaveBuilderResponse create(Builder builder){
var str = autoMapper.getDummyText();
Builder savedBuilder = builderRepository.save(builder);
return new SaveBuilderResponse(savedBuilder);
}
}
And here is the test class that tests the service above:
#SpringBootTest
#RequiredArgsConstructor
#Slf4j
class BuilderServiceImplTest {
#Mock
private BuilderRepository builderRepository;
#Mock
private AdminUserRepository adminUserRepository;
private AutoCloseable autoCloseable;
private BuilderService underTest;
#BeforeEach
void setUp(){
autoCloseable = MockitoAnnotations.openMocks(this);
underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
}
#AfterEach
void tearDown () throws Exception{
autoCloseable.close();
}
#Test
void getByEmail(){}
#Test
#Disabled
void getAll() { }
#Test
#Disabled
void testGetAll() {}
#Test
void create() {
//given
Builder builder = new Builder();
builder.setName("John Johnson");
builder.setCompanyName("Builders Test");
builder.setEmail("test#builders.com");
//when
underTest.create(builder);
//then
ArgumentCaptor<Builder> builderArgumentCaptor = ArgumentCaptor.forClass(Builder.class);
verify(builderRepository)
.save(builderArgumentCaptor.capture());
Builder captureBuilder = builderArgumentCaptor.getValue();
assertThat(captureBuilder).isEqualTo(builder);
}
}
When I start to run the test class the create method in BuilderServiceImpl fired and on this row:
var str = autoMapper.getDummyText();
I get NullPointerException(autoMapper instance is null).
Here is the definition of AutoMapper class:
#Component
#Slf4j
#RequiredArgsConstructor
public class AutoMapper {
public String getDummyText(){
return "Hello From AutoMapper.";
}
}
As you can see I use #Component annotation to register the AutoMapper class to the IoC container and Autowired annotation to inject it into autoMapper property in BuilderServiceImpl class.
Why autoMapper instance is null? How can I make autoMapper to be initialized?
In order to make #Autowire work you have to use the instance of BuilderServiceImpl (object under test) created by spring itself.
When you create the object like this (by yourself, manually):
#BeforeEach
void setUp(){
....
underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
}
Spring doesn't know anything about this object, hence Autowiring won't work
Another thing that might be useful:
You've used #Mock for BuilderRepository and AdminUserRepository.
This are plain mockito annotation, and if you're using an integration/system test that runs the spring under the hood, probably this is not what you want:
Surely, it will create a mock, but it won't put it onto an application context, and won't substitute the beans of these classes that might have been created by spring.
So if this is what you want to achieve, you should use #MockBean instead.
This annotation belongs to Spring Testing framework rather than a plain mockito annotation.
All-in-all you might end up with something like this:
#SpringBootTest
class MyTest
{
#MockBean
BuilderRepository builderRepo;
#MockBean
AdminUserRepository adminUserRepo;
#Autowired // spring will inject your mock repository implementations
// automatically
BuilderServiceImpl underTest;
#Test
void mytest() {
...
}
}
Add #Autowire annotation Annotations on below fields. Error due your not initialized below object In BuilderServiceImpl
#Autowire
private final BuilderRepository builderRepository;
#Autowire
private final AdminUserRepository adminUserRepository;
Why are you creating BuildService manually? If you do this, set AutoMapper manualy too.
#BeforeEach
void setUp(){
autoCloseable = MockitoAnnotations.openMocks(this);
underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
underTest.setAutoMapper(new AutoMapper());
}
You are not using di.

#MockBean and #Autowired of the same service in one test class

Is it possible to somehow have in the same test class #MockBean and #Autowired of the same service?
In other words, I would like to have #MockBean service only for one test, while for others tests of the same class I need it as #Autowired.
This relies on the difference between #MockBean and #Autowired.
#Autowired
only does a lookup in the SpringContext for a bean of that type. This means that you will need to create that bean if you need to 'autowire' it
#MockBean
does exactly what you expect from the name, it creates a 'mock' of the service, and injects it as a bean.
so this
class MyTest {
#MockBean
MyService myService;
}
is equivalent to this
#Import(MyTest.Config.class)
class MyTest {
#Autowired
MyService myService;
#TestConfiguration
static class Config {
#Bean
MyService myService() {
return Mockito.mock(MyService.class);
}
}
}
So, if you need to have a different bean of the MyService type in other tests, you need to create the bean in a #TestConfiguration annotated class
#Import(MyTest.Config.class)
class MyTest {
#Autowired
MyService myService;
#TestConfiguration
static class Config {
#Bean
MyService myService() {
return new MyServiceImpl();
}
}
}
Or, in a class annotated with #Configuration
#Import(MyConfig.class)
class MyTest {
#Autowired
MyService myService;
}
#Configuration
public class MyConfig {
#Bean
MyService myService() {
return new MyServiceImpl();
}
}
The best solution is to change #MockBean to #SpyBean. And in the method you will be able to do like this:
kotlin
#SpyBean
lateinit var serviceMock: Service
#Test
fun smallTest()
`when`(serviceMock.doSomething())
.thenReturn(false)
// your test logic
}
I suspect that the source of the evil here is field injection.
Olvier Gierke (now Drotbohm) wrote a blog post about why field injection is evil.
If you can switch to constructor injection you can mock the service just in your test and pass the mock to the class you want to test.
I just want to leave this answer here as a suggestion for others who might have the chance to use constructor injection instead.

mockito spy does not work on a factory bean method

I have a springboot that I am writing unit test for. There is a factoryBean out of which I get service object in runtime. I want to test that a specific method on this service object is invoked. Here is the app code
#Component
public class AppClient {
#Autowired
ServiceFactory factory
Service secretService
#postContruct
public void init(){
this.secretService=factory.get("secret");
}
public void process(Map<String, Object> param){
for (String key: param.keySet()){
if (key.equals("foobar")){
restService.handle(param.get(key));
}
}
}
}
Here is the unit test I have
#RunWith(SpringRunner.class)
#SpringBootTest
public class AppTest {
#Autowired
AppClient appClient;
#SpyBean
ServiceFactory factory;
Service secretService;
#Before
public void init(){
this.secretService=Mockito.spy(factory.get("secret"));
}
#Test
public void testProcess() {
Object obj = new MyDummyObject();
Map<String, Object> params = new HashMap<>();
params.put("foobar", obj);
appClient.process(params);
Mockito.verify(secretService).handle(obj);
}
}
The test fails and when I run through debugger, I see that handle is invoked. so what is wrong here?
EDIT
#MockBean
ServiceFactory factory;
#Mock
Service secretService
#Before
public void init(){
Mockito.when(factory.get(eq("secret"))).thenReturn(secretService);
}
with this change, factory bean is mocked but secretService is null inside in AppClient. that is, secretService is not being stubbed in. tested through debugger.
The PostConstruct callback executes before the spring application entirely runs and before your test-class make some preparations on the mock of the factory. You can't be able to declare Mockito when().then() expectations on the code which runs in the PostConstruct callback.
I can suggest you make a constructor based injection in the AppClient bean:
#Component
public class AppClient {
private final ServiceFactory factory
#Autowired
public AppClient(ServiceFactory factory){
this.factory = factory;
}
...
}
and test this as a simple unit-test. By manually creating an instance of the AppClient, injecting a mock of the factory, execute the init method and verifying all that you need:
#Test
void initTest(){
when(factory.get(..)).thenReturn(..);
AppClient client = new AppClient(factory);
client.init();
verify(..)
}

Why spring automatically #Autowitred fields in non-managed classes

As far as I know spring provides some ways to inject beans into non-managed classes.
It can be done explicitly with AutowireCapableBeanFactory. (How to inject dependencies into a self-instantiated object in Spring?)
But I've faced strange (IMHO) behavior, when spring performs such injection automatically.
Here is an example with spring batch,
Configuration:
#SpringBootConfiguration
public class ProcessorJobConfig {
//.....
#Bean(name = "pullRestTemplate")
public RestTemplate createPullRestTemplate() {
RestTemplate restTemplate = restTemplateBuilder.build();
return restTemplate;
}
#Bean(name = "step")
public Step step(#Autowired ItemReader<Measurement> itemReader,
#Autowired ItemProcessor<Measurement, Event> itemProcessor,
#Autowired ItemWriter<Event> itemWriter) {
return stepBuilderFactory.get("step")
.<Measurement, Event>chunk(Integer.MAX_VALUE)
.reader(itemReader)
.processor(itemProcessor)
.writer(itemWriter)
.build();
}
#Bean(name = "restProcessorJob")
public Job job(#Qualifier("step") Step step) throws Exception {
return jobBuilderFactory.get("restProcessorJob")
.start(step)
.build();
}
#Bean
public ItemReader<Measurement> itemReader() {
RestMeasureReader restMeasureReader = new RestMeasureReader(); // Use new() explicitly
return restMeasureReader;
}
//.....
}
Reader:
public class RestMeasureReader implements ItemReader<Measurement> {
private static final Logger LOGGER = LoggerFactory.getLogger(RestMeasureReader.class);
/**
* NOTE: This field will be injected automatically by spring, even we are using new() to create instance of this class.
*/
#Autowired
#Qualifier("pullRestTemplate")
private RestTemplate restTemplate;
#Override
public Measurement read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
// do some stuff
}
}
And application itself
#EnableBatchProcessing
#EnableTask
#SpringBootApplication
public class TestAutowiredTaskApplication {
public static void main(String[] args) {
SpringApplication.run(TestAutowiredTaskApplication.class, args);
}
}
Even I use explicit new() to instantiate RestMeasureReader, its RestTemplate field will be injected afterwards.
Is it normal behavior? I do not expect spring to automatically inject fields when creating object with new().
If you are talking about using new inside of your #Configuration class, then yes it is normal behavior. This is you Spring java configs. So it's is Spring managed context. You are not going to call itemReader() in your code explicitly.
So, when you are going to do this:
#Autowired
private ItemReader<Measurement> iterReader;
you will get instance of your RestMeasureReader from Spring's IoC.
But if you will try to do explicitly call new RestMesureReader() inside of your code, you will get a new instance of RestMesureReader not a Spring Proxy with injected #Autowired fields.
Try to remove #Bean from your itemReader() method declaration and won't event be able to autowire RestMesureReader.
So basically #Configuration classes are just a Spring configuration, not a real java code. Even though you call new Spring will still return you a proxy class.
For more information check this guide.
Spring processes beans returned by methods that are annotated with #Bean
This allows you to use autowiring or livecycle callbacks when using Java configuration.
A more minimalistic example:
#Configuration
public class MyConfiguration {
#Bean
public A a() {
return new A();
}
static class A {
#Autowired
private B b;
#PostConstruct
public void onPostConstruct() {
System.out.println("postConstruct: " + b);
}
}
#Component
static class B {
}
}
Here, even if the bean named a is created manually, Spring will inject dependencies (b) and call #PostConstruct callbacks.

How to disable Spring autowiring in unit tests for #Configuration/#Bean usage

I want configure a component test using spring-test configuration inner class (#Configuration). Tested components has some services which I'd like to mock for the test. These services are classes (no interface used) and have spring annotations (#Autowired) in them. Mockito can easily mock them, however, I found no way of disabling spring autowiring.
Example how I can easily reproduce:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = SomeTest.Beans.class)
public class SomeTest {
// configured in component-config.xml, using ThirdPartyService
#Autowired
private TestedBean entryPoint;
#Test
public void test() {
}
#Configuration
#ImportResource("/spring/component-config.xml")
static class Beans {
#Bean
ThirdPartyService createThirdPartyService() {
return mock(ThirdPartyService.class);
}
}
}
public class ThirdPartyService {
#Autowired
Foo bar;
}
public class TestedBean {
#Autowired
private ThirdPartyService service;
}
In this example "TestBean" represents the service to be mocked. I would NOT like "bar" to be injected by spring! #Bean(autowire = NO) does not help (in fact, that's the default value).
(Please save me from "use interfaces!" comments - the mocked service can be 3rd party which I can't do anything with.)
UPDATE
Springockito partially solves the problem, as long as you don't have to have anything else to configure (so you can't use configuration class with Springockito - it does not support it), but use mocks only.
Still looking for pure spring solution, if there's any...
Here is my solution to your problem:
import static org.mockito.Mockito.mockingDetails;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class MockitoSkipAutowireConfiguration {
#Bean MockBeanFactory mockBeanFactory() {
return new MockBeanFactory();
}
private static class MockBeanFactory extends InstantiationAwareBeanPostProcessorAdapter {
#Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return !mockingDetails(bean).isMock();
}
}
}
and then just
#Import(MockitoSkipAutowireConfiguration.class)
in your test #Configuration and you are all set
I solved it by creating FactoryBean for my bean instead of just mocking bean. At this way Spring don't try to autowire fields.
Factory bean helping class:
public class MockitoFactoryBean<T> implements FactoryBean<T> {
private final Class<T> clazz;
public MockitoFactoryBean(Class<T> clazz) {
this.clazz = clazz;
}
#Override public T getObject() throws Exception {
return mock(clazz);
}
#Override public Class<T> getObjectType() {
return clazz;
}
#Override public boolean isSingleton() {
return true;
}
}
Actual test context part:
#Configuration
public class TestContext {
#Bean
public FactoryBean<MockingService> mockingService() {
return new MockitoFactoryBean<>(MockingService.class);
}
}
Check Spring profiles. You don't need to disable auto wiring, you need to inject different beans for different configuration.
You could add the mocked service manually to the spring application context via org.springframework.beans.factory.config.SingletonBeanRegistry#registerSingleton. This way the mock is not post-processed by spring and spring does not attempt to autowire the mock. The mock itself will be injected into your tested bean.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = SomeTest.Beans.class)
public class SomeTest {
// configured in component-config.xml, using ThirdPartyService
#Autowired
private TestedBean entryPoint;
#Autowired
private ThirdPartyService thirdPartyServiceMock;
#Test
public void test() {
}
#Configuration
static class Beans {
#Autowired
private GenericApplicationContext ctx;
#Bean
TestedBean testedBean() {
ctx.getBeanFactory().registerSingleton("thirdPartyService", mock(ThirdPartyService.class));
return new TestedBean();
}
}
public static class ThirdPartyService {
#Autowired
Object bar;
}
public static class TestedBean {
#Autowired
private ThirdPartyService service;
}
}
I am in quite the same situation.
What I found that if you do not set the context loader by #ContextConfiguration annotation on your test class, the default context loader will be used, which derived from AbstractGenericContextLoader. I had a look at its source and turned out it registers all the bean post processors which are responsible for reading annotations such #Autowired. In other words, annotation config is enabled by default.
So the main problem is that there are two configurations which are in conflict: in the java config we said that autowiring is not needed, while the autowired annotation tells the opposite. The real question is how to disable the annotation processing in order to eliminate the undesired configuration.
As far as I know there is no such spring implementation of ContextLoader which would not be derived from AbstractGenericContextLoader so I guess the only we can do is to write our own. It would be something like this:
public static class SimpleContextLoader implements ContextLoader {
#Override
public String[] processLocations(Class<?> type, String... locations) {
return strings;
}
#Override
public ApplicationContext loadContext(String... locations) throws Exception {
// in case of xml configuration
return new ClassPathXmlApplicationContext(strings);
// in case of java configuration (but its name is quite misleading)
// return new AnnotationConfigApplicationContext(TestConfig.class);
}
}
Of course it would be worth to spend more time to find out how to implement ContextLoader properly.
Cheers,
Robert
There are so many ways of doing this, I'm pretty sure that this answer will be incomplete, but here are a few options...
As currently seems to be recommended practice, use constructor injection for your services rather than autowiring the fields directly. This makes testing like this so much easier.
public class SomeTest {
#Mock
private ThirdPartyService mockedBean;
#Before
public void init() {
initMocks(this);
}
#Test
public void test() {
BeanUnderTest bean = new BeanUnderTest(mockedBean);
// ...
}
}
public class BeanUnderTest{
private ThirdPartyService service;
#Autowired
public BeanUnderTest(ThirdPartyService ThirdPartyService) {
this.thirdPartyService = thirdPartyService;
}
}
By doing that, you can also mix up autowired and mocked services by autowiring into the test itself and then constructing the beans under test with the most useful mix of autowired and mocked beans.
A reasonable alternative is to use Spring profiles to define stub services. This is particularly useful when wish to use the same stubbed features in multiple tests:
#Service
#Primary
#Profile("test")
public class MyServiceStub implements MyService {
// ...
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = SomeTest.Beans.class)
#ActiveProfiles({"test"})
public class SomeTest {
// ...
}
By using the #Primary annotation, it ensures that this stub bean will be used instead of any other bean implementing the MyService interface. I tend to use this approach for things like email services, where by changing profile, I'm able to switch between a real mail server and Wiser.

Categories