how to mock private variable in RestController class - java

I am doing some web unit testing with Spring. is there a way I can mock the instance of MyProcessor which get set in #PostConstruct? I tried with #MockBean but it gets set as null and getting null pointer exception?
I am using a factory to instantiate MyProcessor based on some boolean flag. But if there is a different approach altogether that would make my test cleaner, I am open to ideas.
Please Note I am using Junit 5.
public class Controller {
private final AProcessorFactory factory;
#Value("${tt.f.processor.async}")
private boolean isAsync;
private MyProcessor myProcessor;
public Controller(final AProcessorFactory factory) {
this.factory= factory;
}
#PostConstruct
public void init() {
myProcessor = factory.getInstance(isAsync);
}
}
#WebMvcTest(Controller.class)
public class ControllerTest{
#MockBean
private MyProcessor processor;
#MockBean
private AProcessorFactory factory;
#Autowired
private MockMvc mockMvc;
#Test
public void test() throws Exception {
when(processor.process(any(Request.class), any(String.class)))
.thenReturn(new TestResponse("123", SUCCESS, "", ""));
}

It looks like your myProcessor is actually built by your AProcessorFactory in a PostConstruct init() method.
You'll want to provide a behavior for your AProcessorFactory mock.
First, you would probably want to set up your myProcessor in your constructor as the #PostConstruct init() method has no special context loading logic with it.
public class Controller {
#Value("${tt.f.processor.async}")
private boolean isAsync;
private MyProcessor myProcessor;
public Controller(final AProcessorFactory factory) {
this.myProcessor = factory.getInstance(isAsync);
}
}
You can specify this in a #Before step in your test.
#MockBean
private AProcessorFactory factory;
#Before
public void setUp() {
when(processor.process(any(Request.class), any(String.class)))
.thenReturn(new TestResponse("123", SUCCESS, "", ""));
when(factory.getInstance(any())).thenReturn(processor)
}
Spring will register your AProcessorFactory mock that has been loaded with the appropriate behaviors.

Related

JUnit5 test case calls #Transactional function

Given a TestClass with TestMethod for an integration test (spawning MySQL Testcontainer behind the scenes)
#SpringBootTest
public class InsertAnythingIntegrationTest {
private DoAnythingHandler handler;
#Autowired
private AnythingRepository anythingRepository;
#BeforeEach
void beforeEach() {
handler = new DoAnythingHandler(anythingRepository);
}
#Test
void handle_shouldAddEntry_givenValidValue() {
handler.insertSomething(new Entity(x,y,z));
assertThat(anythingRepository.findAll()).isEqualTo(1);
}
}
and the Handler implementation annotated with #Transactional
#Component
public class DoAnythingHandler() {
#Autowired
private AnythingRepository anythingRepository;
#Autowired
private ApplicationEventPublisher applicationEventPublisher;
#Transactional
public void insertOrUpdateSomething(Entity entity) {
var existingEntity = anythingRepository.findById(entity.getId());
if (existingEntity != null) {
existingEntity.valueX = entity.x;
existingEntity.valueY = entity.Y;
existingEntity.valueZ = entity.Z;
} else {
anythingRepository.save(entity);
}
applicationEventPublisher.publishEvent(new AnyFurtherEventUpdatingDb(entity.X, entity.Y));
}
}
If I run this test, the transaction is never opened since it needs to be called from Bean to Bean to apply the #Transactional annotation.
How can I mock a Bean calling this method in my test case.
I don't want my test case to be #Transactional since I am not able to assert anything. This handler is my UnitOfWork and I want no further abstraction layer whatever in place.
How to approach this?
#Autowired
private DoAnythingHandler handler;
did the trick for me.
I thought I tried this before, but maybe I did something wrong before.
Thanks to everyone!

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.

Unable to wire in dependency using MockBean in WebMVCTest

I have a controller class:
public class Controller {
private final IProcessor processor;
public Controller (final ProcessorFactory factory) {
this.processor = factory.getInstance();
}
}
A Factory class to provide the different instances of IProcessor:
#Component
public class ProcessorFactory {
private final Dep1 dep1;
private final Dep2 dep2;
public ProcessorFactory (final Dep1 dep1,
final Dep2 dep2) {
this.dep1= dep1;
this.dep2= dep2;
}
public IProcessor getInstance() {
if (...) {
return new ProcessorA(dep1, dep2);
}
return new ProcessorB(dep1, dep2);
}
}
In my mockito test class where I use Junit5, I am not able to instantiate the IProcessor member and is null:
#WebMvcTest(Controller.class)
public class ControllerTest {
#MockBean
private ProcessorFactory processorFactory ;
#MockBean
private IProcessor processor;
#Autowired
private MockMvc mockMvc;
#Test
public void test1() throws Exception {
when(processor.process(any(Request.class), any(String.class)))
.thenReturn(new BlaBla("Test", "Test"));
String request = ...
this.mockMvc.perform(post("/test/test").contentType(MediaType.APPLICATION_JSON).content(request))
.andDo(print())
.andExpect(status().is2xxSuccessful());
}
}
I am not sure I am using MockBean correctly. Basically I want to mock both the Factory and the Processor.
Since you need to call a mocked method (getInstance()) during Spring context initialization (inside the Controller's constructor), you need to mock the said method in a different way. The mocked bean has to be not only provided as an existing object, but also it should have it's mocked behavior defined.
Addtionally, IProcessor implementations are not configured as Spring beans, so Spring will not inject them - ProcessorFactory calls new explicitly and creates the objects without Spring involvement.
I've created a simple project to reproduce your problem and provide a solution - you can find it here on GitHub if you want to check if the whole thing is working, but here's the most important test snippet (I've simplified the methods a bit):
#WebMvcTest(Controller.class)
class ControllerTest {
private static final IProcessor PROCESSOR = mock(IProcessor.class);
#TestConfiguration
static class InnerConfiguration {
#Bean
ProcessorFactory processorFactory() {
ProcessorFactory processorFactory = mock(ProcessorFactory.class);
when(processorFactory.getInstance())
.thenReturn(PROCESSOR);
return processorFactory;
}
}
#Autowired
private MockMvc mockMvc;
#Test
void test1() throws Exception {
String result = "this is a test";
when(PROCESSOR.process(any()))
.thenReturn(result);
mockMvc.perform(post("/test/test")
.contentType(MediaType.APPLICATION_JSON)
.content("{}"))
.andDo(print())
.andExpect(status().is2xxSuccessful())
.andExpect(content().string(result));
}
}

Mockito cannot inject mocks for #Async method

I have a class
#EnableAsync
class A {
#Autowired
private SomeService someService;
#Async
public void someMethod() {
this.someSerivice.call();
}
}
class ATest {
#Before
public void before() {
MockitoAnnotations.init(this)
}
#Autowired
#InjectMocks
private A a;
#Mock
private SomeService someService;
#Test
public void someTest() {
}
}
In the above example, someService should be mocked by Mockito. However, due to presence of #Async, it goes not get mocked and I recieve the actual instance.
Has anyone faced this? Any solutions?
You can set the mock manually like below in your test case
ReflectionTestUtils.setField(a, "someService", someService);

How to prevent Spring from injecting #Autowired references inside a mock?

I want to test a class using Spring + JUnit + Mockito but I don't manage to make it work properly.
Let's say my class references a Service:
#Controller
public class MyController
{
#Autowired
private MyService service;
#PostConstruct
public void init() {
service.whatever();
}
public void doSomething() {
service.create();
}
}
And this Service references a Repository:
#Service
public class MyService {
#Autowired
private MyRepository repository;
public void whatever() {}
public void create() {
repository.save();
}
}
When testing the MyController class, I want the service to be mocked. The problem is: even when the service is mocked, Spring tries to inject the repository in the mock.
Here is what I did. Test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyControllerTestConfiguration.class })
public class MyControllerTest {
#Autowired
private MyController myController;
#Test
public void testDoSomething() {
myController.doSomething();
}
}
Configuration class:
#Configuration
public class MyControllerTestConfiguration {
#Bean
public MyController myController() {
return new MyController();
}
#Bean
public MyService myService() {
return Mockito.mock(MyService.class);
}
}
And the error I get: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [test.test.MyRepository] found for dependency
I tried to initialize the mock using Mockito's #InjectMocks annotation but this fails because the #PostConstruct method is called before the mocks injection, generating a NullPointerException.
And I cannot simply mock the repository because in real life that would make me mock A LOT of classes...
Can anyone help me on this?
Use constructor instead of field injection. That makes testing a lot easier.
#Service
public class MyService {
private final MyRepository repository;
#Autowired
public MyService(MyRepository repository) {
this.repository = repository;
}
public void whatever() {}
public void create() {
repository.save();
}
}
-
#Controller
public class MyController {
private final MyService service;
#Autowired
public MyController(MyService service) {
this.service = service;
}
#PostConstruct
public void init() {
service.whatever();
}
public void doSomething() {
service.create();
}
}
This has several advantages:
You don't need Spring in your tests. This allows you to do proper unit tests. It also makes the test incredibly fast (from seconds to milliseconds).
You cannot accidentally create an instance of a class without its dependencies which would result in a NullPointerException.
As #NamshubWriter pointed out:
[The instance fields for the dependencies] can be final, so 1) they cannot be accidentally modified, and 2) any thread reading the field will read the same value.
Discard the #Configuration class and write a test like this:
#RunWith(MockitoJUnitRunner.class)
public class MyControllerTest {
#Mock
private MyRepository repository;
#InjectMocks
private MyService service;
#Test
public void testDoSomething() {
MyController myController = new MyController(service);
myController.doSomething();
}
}
Use interfaces, especially if you use some kind of AOP (transactions, security, etc), i.e. you'll have interface MyService and class MyServiceImpl.
In configuration you'll have:
#Bean
public MyService myService() {
return Mockito.mock(MyService.class);
}
you should put the #InjectMocks annotation in your controller and #Mock in your service, look:
#Autowired
#InjectMocks
private MyController myController;
#Autowired
#Mock
private MyService myService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testDoSomething() {
myController.doSomething();
}

Categories