mockito spy does not work on a factory bean method - java

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(..)
}

Related

Stubbing methods of injected beans in another bean's constructors

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)

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.

Mock Method of authowired bean using mockito and spring mock

I have to impelement a test for MyService that conntain two methods method1 & method2:
and the method1 call method2 (method1 --> method2 )
so i've somthing like this in my test class
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = SpringBootApplicationTest.class)
#ContextConfiguration(classes = { conf.class })
public class CommonCAMServiceTest {
#Autowired
private MyService myService;
test_method_1(){...}// this is what i want to implement and i have to mock the method2 call
test_method_2(){...}//this work fine
...
so i want to test my method1 but with the mock of method ( even my service class is autowired not mocked)
Thanks
Mockito supports what I will call "partial mocking";
it is called Spy.
Instead of creating a mock bean for your service,
create a Spy.
Also,
as mentioned in other answers,
don't user #Autowire for the service.
Here is some example code:
public class CommonCAMServiceTest
{
#Spy
private MyService myService;
#Before
public void before()
{
MockitoAnnotations.initMocks(this);
// Mock method2 for every test.
doReturn(something).when(myService).method2();
}
#Test
public void someTestName()
{
// Mock method2 in this test.
doReturn(somethingElse).when(myService).method2();
... call method1 to do stuff.
}
}
Create two Services for those two Methods. Then you can mock one service to test the other.
Lets assume that your Service look like that:
#Service
public class MyService {
public void method1() {
method2();
}
public void method2() {
System.out.println("calling method 2");
}
}
So if you willing to Mock the method2 function you we'll need to use a Mocked Bean for that using Spring & Mockito
#MockBean // Use that instead of #Autowired, will create a Mocked Bean
public MyService service;
#Test
public void testing() {
Mockito.doAnswer((Answer<Void>) invocation -> {
System.out.println("mocked");
return null;
}).when(service).method2();
Mockito.doCallRealMethod().when(service).method1();
service.method1();
}

Excluding an ApplicationListener #Component in Spring Boot during tests

I am trying to have my test unit up and running, and I have encountered a weird issue. My application uses an ApplicationListener class annotated as a #Component to perform an operation during startup.
During tests I have mocked the service that contains the logic, but I found that even though Mockito's when instructions work well in controller scope, the bean is not initialized for this ApplicationListener class: instead of returning what I define in the test unit, it returns either false or null - depending on the data type returned by each method in the service.
Since I have not found any way to initialize the mocked service from the test unit for the ApplicationListener class, I have decided to exclude it. To do so I have tried different approaches, being the one most often used that of creating a test application context and change its configuration. Unfortunately, nothing I have seen is working - so I am here asking for help. If possible, I would prefer not touching the ApplicationListener class and do all related coding in the test code.
I am interested in any of the two possible solutions, if they can be done:
1.- Get the mocked behaviour during the ApplicationListener execution, but I have read somewhere that this cannot be done
2.- Exclude the #Component from the test unit somehow.
TestUnit.Java:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = TestApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class TestConfigurationService {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#MockBean
private MockService mockService;
private void initMockBean () throws Exception {
when(mockService.isDoingSomething()).thenReturn(true);
}
#Before
public void setup() throws Exception {
// Spring mock context application setup
this.mockMvc = webAppContextSetup(webApplicationContext).build();
// Initialize ConsulService mock bean
initMockBean ();
}
}
TestApplication.java
#SpringBootApplication
#EnableAutoConfiguration
#ComponentScan(basePackages="my.base.package", excludeFilters = #Filter(type = FilterType.ASSIGNABLE_TYPE, classes = StartupConfiguration.class))
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
Besides what is shown in the code, I have also tried this annotation in file TestApplication.java:
#SpringBootApplication(exclude={StartupConfiguration.class})
StartupConfiguration.java
#Component
public class StartupConfiguration implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
private ConfigurationService configurationService;
#Override
public void onApplicationEvent(final ContextRefreshedEvent event) {
try {
configurationService.updateConfiguration();
} catch (Exception e) {
throw new RuntimeException ("Error", e);
}
}
}
ConfigurationService.java
public interface ConfigurationService {
public void updateConfiguration () throws Exception;
}
ConfigurationServiceImpl.java
#Service
#Transactional
public class ConfigurationServiceImpl implements ConfigurationService {
#Autowired
private MService mockService;
#Override
public void updateConfiguration() throws Exception {
if (mockService.isDoingSomething()==false)
throw new Exception ("Something went wrong");
}
}
Versions:
Spring Boot 1.5.4.RELEASE,
Java 1.8
You can create mock bean of the same type and mark it with #Primary annotation to replace real bean. You can achieve this by having test such configuration:
#Configuration
#Import(TestApplication.class)
public class TestConfiguration {
#Bean
#Primary
public ConfigurationService configurationService() {
return Mockito.mock(ConfigurationService.class);
}
}
then get this mock in test:
...
public class TestConfigurationService {
...
#Autowired
ConfigurationService configurationService;
#Before
public void setUp() {
when(mockService.isDoingSomething()).thenReturn(true);
}
}
Thanks, araxn1d. Your answer gave me the clue to solve this issue.
I mocked the StartupConfiguration class in TestUnit.java:
#MockBean
private StartupConfiguration startupConfiguration;
Though in this case I was lucky: application listeners don't have returning methods, so they don't need when test configuration. If I had required that some method there returned for example true or a value, this method would not apply.
But at least for application listeners, this is enough.

Mocking Spring bean's method behavior breaks aspects

I searched SO and found bunch of other questions that looked similar but not exactly, so I'll ask another one.
I have Spring application and say I created custom aspect (looking for CatchMe annotation) to log exceptions in a specific way. I want to test the aspect by mocking the behavior of one of my Spring #Service class's method so it throws exception when it is called. Then in another method, annotated with my custom annotation #CatchMe, I call the first method. What I expect to happen is the exception to get logged. Unfortunatelly the exception is thrown but the aspect is not triggered. So how can I make the aspect to get triggered in this test using Mockito?
Note: I've checked those (plus a bunch more):
Unit testing Spring #Around AOP methods
Spring Aspect not triggered in unit test
Spring: cannot inject a mock into class annotated with the #Aspect annotation
but most of them are Controller related and not Service related and I want to test only the service.
The Test
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {BeanConfig.class})
public class MyServiceTest {
#Autowired
#InjectMocks
private MyService service;
#Mock
private MyServiceDependency serviceDep;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(service, "serviceDep", serviceDep);
}
#Test
public void test() {
when(serviceDep.process()).thenAnswer(new Answer<Object>() {
#Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
throw new Exception("Sample message.");
}
});
service.execute();
}
}
Services
#Service
public class MyService {
#Autowired
private MyServiceDependency serviceDep;
#CatchMe
public void execute() {
serviceDep.process();
}
}
#Service
public class MyServiceDependency {
public Object process() {
// may throw exception here
}
}
Configuration and Aspect
#Configuration
#EnableAspectJAutoProxy
#ComponentScan(basePackages = {"com.example.services"})
public class BeanConfig { .. }
#Aspect
#Component
public class CatchMeAspect {
#Around("#annotation(CatchMe)")
public Object catchMe(final ProceedingJoinPoint pjp) throws Throwable {
try {
pjp.proceed();
} catch (Throwable t) {
// fency log
}
}
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface CatchMe {}
EDIT: The functionality works but I want to verify it with the test.
Actually it is working as expected, however you are running in a side effect of proxy based AOP, especially class based proxies in this case.
Currently you are setting the field on the proxy and not on the actual object inside the proxy. Which is what you actually want. To obtain the actual instance use AopTestUtils.getUltimateTargetObject and then use that in the ReflectionTestUtils.setField method.
#Autowired
#InjectMocks
private MyService service;
#Mock
private MyServiceDependency serviceDep;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
MyService serviceToInject = AopTestUtils.getUltimateTargetObject(service);
ReflectionTestUtils.setField(serviceToInject, "serviceDep", serviceDep);
}
However I think that approach is wrong, when you start messing around like this there is a better way. Simply use Spring to inject the mock. Create a specific #Configuration class for this test case. Make it a internal public static class and for the dependency add a mocked #Bean.
#Configuration
#Import(BeanConfig.class)
public static class TestBeanConfig {
#Bean
public MyServiceDependency myServiceDependency() {
return Mockito.mock(MyServiceDependency.class);
}
}
Now in your test class you can simply #Autowire both beans and not need to use reflection or whatever to set dependencies.
#RunWith(SpringJUnit4ClassRunner.class)
public class MyServiceTest {
#Autowired
private MyService service;
#Autowired
private MyServiceDependency serviceDep;
#Test
public void test() {
when(serviceDep.process()).thenAnswer(new Answer<Object>() {
#Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
throw new Exception("Sample message.");
}
});
service.execute();
}
}
Which will take care of the correct dependencies.
I had the same problem as #nyxz and this is intentional, see https://github.com/spring-projects/spring-boot/issues/7243.
Inspired by #M. Deinum following solution worked for me with Spring Boot 2.3.4.RELEASE and JUnit 5.
We will just provide a mocked bean without #MockedBean
#SpringBootTest
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
class MyServiceTest {
#Autowired
private MyService service;
#Test
public void test() {
service.execute();
}
static class TestBeanConfig {
#Bean
#Primary
public MyServiceDependency myServiceDependency() {
MyServiceDependency myServiceDependency = Mockito.mock(MyServiceDependency.class)
// Add behavior of mocked bean here
return myServiceDependency;
}
}
}

Categories