Mockito with Service Class Repo Class and EntityManager - java

I have classes Two levels to reach the EntityManager
Service Class
class MyServiceClass {
#Autowired
MyRepositoryInterfaceImpl repo;
void myMethod() {
repo.myMethod();
}
}
Repo Class
class MyRepositoryInterfaceImpl {
#Autowire
EntiryManager em;
void myMethod() {
em.getResultList();
}
}
My question I am able to mock for the class MyRepositoryInterfaceImpl with this code
#ExtendWith(MockitoExtention.class)
class MyRepositoryInterfaceImplTest {
#InjectMocks
MyRepositoryInterfaceImpl repo;
#Mock
EntiryManager em;
}
above code works fine
But I don't want to do that, why can't I directly write test code like this
#ExtendWith(MockitoExtention.class)
class MyServiceClassTest {
#InjectMocks
MyServiceClass service;
#InjectMocks
MyRepositoryInterfaceImpl repo;
#Mock
EntiryManager em;
}
and directly mock entity manager from this level ? and call service.myMethod() will it automatically propagate and trigger the entity manager mock ?
but it is not happening that way

This doesn't work because #InjectMock is not treated as #Mock.
From a UnitTest perspective it is desired to only test one thing/unit.
so you only need to mock the Repository and forget about the EntityManger. Instead use Mockito.when(repo.findAll()).thenReturn(Collections.emptyList()) for UnitTests.
When you want to test the integration between the Service- and RepositoryLayer. You can write an IntegrationTest.

Related

Spring Boot test: how to partially mock a service with AOP

I'm trying to test a Spring Boot service that depends on both a repository and another service. I'm using TestContainers to verify that the service interacts correctly with the repository. However, I need to mock the other service in order to provide the test data that will be used. The service method in question needs to be transactional.
#Service
public class MyService {
private MyRepository myRepository; // This needs to be a real repository
private OtherService otherService; // This needs to be mocked
#Autowired
public MyService(MyRepository myRepository, OtherService otherService) {...}
#Transactional
public void methodToTest() {
// Get (mock) data from otherService, and interact with (real) myRepository
}
}
I can get it working in my test by manually creating the service with its dependencies. However, the resulting object is just a POJO that has not been enhanced by Spring to be Transactional:
#SpringBootTest
#ContextConfiguration(initializers = {TestContainersConfig.class})
public class MyServiceTest {
#Autowired
MyRepository myRepository;
OtherService otherService;
MyService myService;
#BeforeEach
void setup() {
otherService = Mockito.mock(OtherService.class);
// This works, except that the object is a POJO and so #Transactional doesn't work
myService = new MyService(myRepository, otherService);
}
#Test
#Transactional
void testMethodToTest() {
// Provide mock data
when(otherSerivce.something()).thenReturn(...);
// Run the test
myService.methodToTest();
// Would like to remove these lines: myService should already do it
TestTransaction.flagForCommit();
TestTransaction.end();
// assertions...
}
}
How can I get Spring to enhance the test MyService instance so that methodToTest will handle the transaction?

#Repository instance is null when invoking a method using a #Service instance from a unit test

My goal is to use an in-memory database for these unit tests, and those dependancies are listed as:
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
runtimeOnly("com.h2database:h2")
So that the repository instance actually interacts with a DB, and I dont just mock return values.
The problem is that when I run my unit test, the repository instance inside the service instance is null.
Why is that? Am I missing some annotation on the unit test class to initialise the repository instance?
This is the console output when running my unit test:
null
java.lang.NullPointerException
at com.my.MyService.findAll(MyService.java:20)
at com.my.MyTest.testMy(MyTest.java:23)
My unit test class:
public class MyTest {
#MockBean
MyRepository myRepository;
#Test
void testMy() {
MyService myService = new MyService();
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
My service class:
#Service
public class MyService {
#Autowired
MyRepository myRepository;
public List<MyEntity> findAll() {
System.out.println(myRepository); // null
return (List<MyEntity>) myRepository.findAll(); // throws NullPointerException
}
#Transactional
public MyEntity create(MyEntity myEntity) {
myRepository.save(myEntity);
return myEntity;
}
}
My repository class:
#Repository
public interface MyRepository extends CrudRepository<MyEntity, Long> {
}
My entity class:
#Entity
public class MyEntity {
#Id
#GeneratedValue
public Long id;
}
Why is that? Am I missing some annotation on the unit test class to initialise the repository instance?
Basically yes :)
You need to initialise a Spring Context by Annotating your Testclass with #SpringBootTest
The other Problem you have is that you create your MyService Object manually.
By doing so SpringBoot has no chance to inject any Bean for you. You can fix this by simply injecting your MyService in your Testclass. Your Code should look something like this:
#SpringBootTest
public class MyTest {
#Autowired
private MyService myService;
#Test
void testMy() {
int size = myService.findAll().size();
assertEquals(0, size);
}
}
To use #MockBean annotation, you have to use SpringRunner to run the test. Use #RunWith Annotation on top of your test class and pass SpringRunner.class.
#RunWith(SpringRunner.class)
public class MyTest {
#MockBean
MyRepository myRepository;
#Test
void testMy() {
MyService myService = new MyService();
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
The problem here is your service implementation. Using #Autowired to inject the dependency will work when you run the whole app, but it do not allow you to inject a custom dependency when you'll need it, and a good example of this is testing.
Change your service implementation to:
#Service
public class MyService {
private MyRepository myRepository;
public MyService(MyRepository myRepository){
this.myRepository = myRepository;
}
public List<MyEntity> findAll() {
System.out.println(myRepository); // null
return (List<MyEntity>) myRepository.findAll(); // throws NullPointerException
}
#Transactional
public MyEntity create(MyEntity myEntity) {
myRepository.save(myEntity);
return myEntity;
}
}
This constructor will be called by spring. Then change your test to:
public class MyTest {
#Mock
MyRepository myRepository;
#Test
void testMy() {
MyService myService = new MyService(myRepository);
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
Note I have replaced #MockBean to #Mock as the previous annotation is for injecting a mock bean into the spring context, which is not needed if you're doing unit testing. If you want to boot spring context (which I would not recommend you) you need to configure your test class with #SpringBootTest or some of the other available alternatives. That will convert your test into an integration test.
PD: This test will not work if you don't provide a mock to myRepository.findAll(). Mockito default behaviour is to return null, but you're expecting it to return 0, so you'll need to do something like given(myRepository.findAll()).willReturn(0).
I believe you wish to write an integration test. Here you could remove the MockBean annotation and simply autowire your repository. Also, run with The SpringRunner class.
#RunWith(SpringRunner.class)
public class MyTest {
#Autowired
MyRepository myRepository;
#Autowired
MyService myService
#Test
void testMy() {
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
This should work

Unit Test - Null mocked component is injected

I have the following use case:
I have a Test class with 3 components, from which 2 of them are inject into the third; I am using JUnit and Mockito for testing
public class MyTestClass{
#Mock
SomeService someService;
#Mock
AnotherService anotherService;
#InjectMock
MainService mainService;
#BeforeMethod
public void init() {
initMocks(this);
}
#Test
public void test(){
when(someService.someMethod(any())).thenReturn(something);
when(anotherService.someMethod(any()).thenReturn(something);
mainService.someMainMerhod();
// ...other assert logic
}
}
And here I have the MainService Spring component which has injected the two other components
#Component
public class MainService{
#Autowired
private SomeService someService; //Why here I have null component
private AnotherService anotherService; // and here I have an initialized component ???
public MainService(AnotherService anotherService){
this.anotherService = anotherService;
}
// implementation
}
Question 1 : Why someService instance is null when I am using both constructor and #Autowired?
Question 2 : Why if I am using only the constructor without #Autowired and vice versa, everything works, since I do not load the Spring context... I have unit tests...
The Javadoc states:
"Mockito will try to inject mocks only either by constructor injection, setter injection, or property injection in order. If any of the strategy fail, then Mockito won’t report failure; i.e. you will have to provide dependencies yourself."
Hence it will fail silently.

Injecting mocks with Mockito does not work

I'm using Mockito to test my Spring project, but the #InjectMocks seems not working in injecting a mocked service into another Spring service(bean).
Here is my Spring service that I want to test:
#Service
public class CreateMailboxService {
#Autowired UserInfoService mUserInfoService; // this should be mocked
#Autowired LogicService mLogicService; // this should be autowired by Spring
public void createMailbox() {
// do mething
System.out.println("test 2: " + mUserInfoService.getData());
}
}
And below is the service that I want to mock:
#Service
public class UserInfoService {
public String getData() {
return "original text";
}
}
My test code is here:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/root-context.xml" })
public class CreateMailboxServiceMockTest {
#Mock
UserInfoService mUserInfoService;
#InjectMocks
#Autowired
CreateMailboxService mCreateMailboxService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void deleteWithPermission() {
when(mUserInfoService.getData()).thenReturn("mocked text");
System.out.println("test 1: " + mUserInfoService.getData());
mCreateMailboxService.createMailbox();
}
}
but the result would like
test 1: mocked text
test 2: original text // I want this be "mocked text", too
it seems that the CreateMailboxService didn't get the mocked UserInfoService but using Spring's autowired bean.
Why is my #InjectMocks not working?
In my case, I had a similar issue when I worked with JUnit5
#ExtendWith(MockitoExtension.class)
class MyServiceTest {
...
#InjectMocks
MyService underTest;
#Test
void myMethodTest() {
...
}
underTest was null.
The cause of the problem was that I used #Test from JUnit4 package import org.junit.Test; instead JUnit5 import org.junit.jupiter.api.Test;
For those who stumbles on this thread and are running with JUnit 5 you need to replace
#RunWith(SpringJUnit4ClassRunner.class)
with
#ExtendWith(MockitoExtension.class)
#RunWith(JUnitPlatform.class)
Further reading here. Unfortunately there is no hint when executing the test cases with JUnit 5 using the old annotation.
You can create package level setter for mUserInfoService in CreateMailboxService class.
#Service
public class CreateMailboxService {
#Autowired UserInfoService mUserInfoService; // this should be mocked
#Autowired LogicService mLogicService; // this should be autowired by Spring
public void createMailbox() {
// do mething
System.out.println("test 2: " + mUserInfoService.getData());
}
void setUserInfoService(UserInfoService mUserInfoService) {
this.mUserInfoService = mUserInfoService;
}
}
Then, you can inject that mock in the test using the setter.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/root-context.xml" })
public class CreateMailboxServiceMockTest {
#Mock
UserInfoService mUserInfoService;
CreateMailboxService mCreateMailboxService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mCreateMailboxService = new CreateMailboxService();
mCreateMailboxService.setUserInfoService(mUserInfoService);
}
...
}
This way you can avoid problems with #InjectMocks and Spring annotations.
If you are trying to use the #Mock annotation for a test that relies directly on Spring injection, you may need to replace #Mock with #MockBean #Inject (both annotations), and #InjectMocks with #Inject. Using your example:
#MockBean
#Inject
UserInfoService mUserInfoService;
#Inject
CreateMailboxService mCreateMailboxService;
I had a pretty similar situation. I am writing it down just in case any reader is going through the same. In my case I found that the problem was that I was setting my injected variable as final in the local class.
Following your example, I had things like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/root-context.xml" })
public class CreateMailboxServiceMockTest {
#Mock
UserInfoService mUserInfoService;
#InjectMocks
CreateMailboxService mCreateMailboxService = new CreateMailboxService(mUserInfoService);
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void deleteWithPermission() {
...
}
}
But in this class I had it like this:
#Service
public class CreateMailboxService {
private final UserInfoService mUserInfoService; // it is NOT injecting Mocks just because it is final! (all ok with private)
private final LogicService mLogicService; // it is NOT injecting Mocks just because it is final! (all ok with private)
#Autowired
public CreateMailboxService(UserInfoService mUserInfoService, LogicService mLogicService) {
this.mUserInfoService = mUserInfoService;
this.mLogicService = mLogicService;
}
public void createMailbox() {
...
}
}
Just deleting the final condition, #InjectMocks problem was solved.
For those who are running with JUnit 5 you need to replace the #RunWith(SpringJUnit4ClassRunner.class) with #ExtendWith(MockitoExtension.class).
For further reading take a look here.
there is no need of #Autowired annotation when you inject in the test class. And use the mock for the method to get your mocked response as the way you did for UserInfoService.That will be something like below.
Mockito.when(mCreateMailboxService. getData()).thenReturn("my response");
You can use MockitoJUnitRunner to mock in unit tests.
Use #Mock annotations over classes whose behavior you want to mock.
Use #InjectMocks over the class you are testing.
Its a bad practice to use new and initialize classes (better to go for dependency injection) or to introduce setters for your injections. Using setter injection to set dependencies only for tests is wrong as production code should never be altered for tests.
#RunWith(MockitoJUnitRunner.class)
public class CreateMailboxServiceMockTest {
#Mock
UserInfoService mUserInfoService;
#InjectMocks
CreateMailboxService mCreateMailboxService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
...
}

#injectMocks does not work when using spring aop

I'm writing junit and I using #mock and #injectMock.But,I find #injectMocks doesn't work when bean in spring aop.code like this:
QuestionService.java:
#Component
public class QuestionService implements IQuestionService{
#Resource
private IUserService userService;
#Override
public User findUserById(long id) {
// TODO Auto-generated method stub
User user = userService.findUserById(id);
return user;
}
}
Test.java:
#Mock
IUserService mockuserService;
#InjectMocks
#Resource
QuestionService questionService;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
#Test
public void testfind() {
when(mockuserService.findUserById(1)).thenReturn(
new User(1, "name"));
User user = questionService.findUserById(1);
Assert.assertEquals(new User(1, "name"), user);
}
It works!
But,when I add userService in spring aop,It does not work!
For example, transaction aop.
How can I fix it?
I found an interesting behavior - once I used the AOP around any method in the class, the mocks stopped working; instead the 'real' component was initiated although there was no code for that matter.
I found that if you were to use #MockBean - everything works.
Why did you annotate QuestionService with #Resource in test class? Are you running with SpringJUnit4ClassRunner by loading bean configs? If not remove #Resource annotation and try, doesn't matter whether using AOP or not, it should work.
And add below snippet of code in #Before method of your test class as first line.
MockitoAnnotations.initMocks(this);
#InjectMocks : Mark a field on which injection should be performed.
MockitoAnnotations.initMocks(this): initializes fields annotated with Mockito annotations.

Categories

Resources