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

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

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.

Use a real component in a service test

I am testing a service that has an autowired helper component. That component has autowired repo.
In my test, I want to use that component helper, not a mock. And I want to mock the repo for that.
But I can't manage to make it work.
The Service that I test:
#Service
public class ServiceImpl{
#Autowired
private Helper helper;
}
The Helper class that has autowired repo
#Component
public class Helper {
#Autowired
private Repository repo;
}
My test should be like this
#ExtendWith(MockitoExtension.class)
public class ServiceImplTest {
ServiceImpl service;
#Mock
private Repository repoMock;
#InjectMocks
private Helper helper;
}
I'd like better to refactor the whole thing but unfortunately, it's not possible...
Any help welcome.
I would prefer constructor injection over field injection. (read more here)
In this case you classes would look something like this:
#Component
public class Helper {
#Autowired
public Helper(Repository repo) {
this.repo = repo;
}
}
#Service
public class ServiceImpl{
#Autowired
public ServiceImpl(Helper helper) {
this.helper = helper;
}
}
This way you can easily create a real Helper object with a mock Repository object:
ServiceImpl service;
private Helper helper;
#Mock
private Repository repoMock;
#BeforeEach
void init() {
helper = new Helper(repoMock);
service = new ServiceImpl(helper);
}
I finally found a solution, thanks for the help.
#ExtendWith(MockitoExtension.class)
public class ServiceImplTest {
#InjectMocks
ServiceImpl service
#Spy
#InjectMocks
private Helper helper;
#Mock
private Repository repoMock;
#InjectMocks
private Helper helper;
}
That way, the mocked repo is injected in the spy helper, and the helper can be injected in the service.
The #Spy objects are actually instantiated, so if you don't stubb any of its methods, you'll get a "real" object.
Here, the mocked repo is injected in the helper and the helper injected in the service.
If Repository is an interface (and not a concrete class) you can try the following:
#ExtendWith(MockitoExtension.class)
public class ServiceImplTest {
#Spy
#InjectMocks
ServiceImpl service = new ServiceImpl();
#Mock
private Repository repoMock;
#InjectMocks
private Helper helper;
}
Try to load a configuration for your tests which gives priority to a mock repo Tested:
#RunWith(SpringRunner.class)
#SpringBootTest
public class SomeTest {
#Configuration
static class ContextConfiguration {
#Bean
public Helper helper() {
return new Helper();
}
#Bean
#Primary
public Repository repoMock() {
Repo repo = Mockito.mock(Repository.class);
Mockito.when(/* Mock your repo */);
return repo;
}
}
#Autowired
private Helper helper;
#Test
public void testMethod() {
// Your test goes here
}
}
Anyway, keep in mind that field autowiring is evil. Switch to constructor dependency injection ASAP.
See also:
https://stackoverflow.com/a/39042441/1199132

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

Comparing 2 objects using JUnit

I have a service which I want to test:
public class DepartmentServiceImpl implements DepartmentService {
#Autowired
private DepartmentDao departmentDao;
//... other functions
#Override
public Department getDepartment(int depid) {
return departmentDao.getDepartment(depid);
}
}
and here is my test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:sdnext-servlet.xml")
public class TestDepartmentDetails {
Department department = new Department();
DepartmentServiceImpl service = new DepartmentServiceImpl();
#Autowired
private DepartmentDao myDao;
#Test
public void testGetDepartment(){
assertEquals("lalalalalalala", service.getDepartment(98).getDepName());
}
Why it gives me a failure and throws a NullPointerException? Is there any other solution?
Note: This is an application which uses Spring and Hibernate.
Try it this way
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:sdnext-servlet.xml")
public class TestDepartmentDetails {
#Autowired
DepartmentService service;
#Test
public void testGetDepartment(){
assertEquals("lalalalalalala", service.getDepartment(98).getDepName());
}
Here you are creating instances manually which is not the right way while using Spring.
Please refer to the Spring docs below:
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/
You have to autowire DepartmentServiceImpl service instead of DepartmentDao in this case.

autowired dependency not getting mocked while executing test case with junit and mockito

I am using Junit4 and Mockito for test cases, in the following code dbprop.getProperty("config") is throwing a NullPointerException because dbProp is null. Please help me out why it was not mocked?
public abstract class BaseClass {
#Autowired
protected DBproperties dbprop;
}
public class SampleClass extends BaseClass {
#Autowired
private OrderService orderService;
valdiateOrder(String input) {
String config = dbprop.getProperty("config");
}
}
public class TestSampleClass {
#InjectMocks
SampleClass sampleClass;
#Mock
private OrderService orderService;
#Test
public void testValidateOrder() {
DBproperties dbprop = mock(DBproperties .class);
when(dbprop.getProperty("config")).thenReturn("xxxx");
assertNotNull(SampleClass.valdiateOrder("xxx"));
}
}
Your dbprop mock has not been injected into sampleClass, you need to add:
#Mock
private DBproperties dbprop;
Then remove the dbprop mock creation from your test method:
#Test
public void testValidateOrder() {
// DBproperties dbprop = mock(DBproperties .class); <-- removed
when(dbprop.getProperty("config")).thenReturn("xxxx");
assertNotNull(SampleClass.valdiateOrder("xxx"));
}
Next, to ensure mocks are injected when using the #InjectMocks annotations you need to either add the following runner:
#RunWith(MockitoJUnitRunner.class)
public class TestSampleClass {
...
Or call the following in a #Before method:
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
See the MockitoAnnotations and MockitoJUnitRunner JavaDocs for more information on the two approaches.
You can annotate your Object with #Mock, so its look like this
#Mock
DBproperties dbProperties;#Before public void init(){ MockitoAnnotations.initMocks(this);
}

Categories