Own mock of JPA repository in Spring Boot - java

At the beginning i want to admit that i am aware of mockito, EasyMock etc existing.
How can i test my database with OWN mock.
#Entity
public class CLASS{
private Long id;
***constructors/getters/setters****
}
-
public interface CLASSrepo extends JpaRepository<CLASS, Long> {
}
-
#Service
public class CLASSservice {
#Autowired
CLASSrepo classRepo;
}
Any options how to create own mock without using Mockito #MockBean #Mock etc and test service with repo mock?

Related

Spring Mock repository.count() always return 0

I have a spring boot project where I'm trying to mock my repository for tests.
I would like my repository.count() to return 30L but it actually always return 0
#Service
#Transactional
public class DishServiceImpl implements DishService {
private final DishRepository dishRepository;
public DishServiceImpl(DishRepository dishRepository) {
this.dishRepository = dishRepository;
}
#Override
public List<Dish> searchDishes() {
long countDish = dishRepository.count();
System.out.println(countDish);
[...]
}
}
#RunWith(SpringRunner.class)
#SpringBootTest(classes = WhatAreWeEatingApp.class)
#Transactional
public class DishServiceTest{
#Mock
private DishRepository dishRepository;
#Autowired
private DishService dishService;
#Test
public void test(){
when(dishRepository.count()).thenReturn(30L);
dishService.searchDishes();
[...]
}
You repository mock is never set as dependency to the bean service.
Here you mock in the frame of a running Spring container :
#RunWith(SpringRunner.class)
#SpringBootTest(classes = WhatAreWeEatingApp.class)
It is not unit test. So you want to use #MockBean from Spring Boot to mock a bean in the container, not #Mock from Mockito to mock instances created outside the container.
Don't like auto promotional post but this question should help you.
To go further you should not need to run a container to test the service method. So you should probably remove the Spring Boot test annotation and write a real unit test.

unit test a interface implementation with mock in Spring Boot

I'm trying to write a simple unit test for a service in Spring Boot.
The service calls a method on a repository which returns an instance of User.
I'm trying to mock the repository, because I want to test only the service.
So, the code for Repository:
public interface UserRepository extends MongoRepository<User, String> {
User findByEmail(String email);
}
Service interface:
public interface UserService {
#Async
CompletableFuture<User> findByEmail(String email) throws InterruptedException;
}
Service implementation:
#Service
public class UserServiceImpl implements UserService {
private UserRepository userRepository;
// dependency injection
// don't need Autowire here
// https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-spring-beans-and-dependency-injection.html
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Async
public CompletableFuture<User> findByEmail(String email) throws InterruptedException {
User user = userRepository.findByEmail(email);
return CompletableFuture.completedFuture(user);
}
}
Unit Test:
#RunWith(SpringRunner.class)
#SpringBootTest
public class UserServiceTest {
#InjectMocks
UserService userService;
#Mock
UserRepository mockUserRepository;
#Before
public void setUp() {
MockitoAnnotations.initMock(this);
}
#Test
public void mustReturnUser() throws InterruptedException {
String emailTest = "foo#bar.com";
User fakeUser = new User();
fakeUser.setEmail(emailTest);
when(mockUserRepository.findByEmail(emailTest)).thenReturn(fakeUser);
User user = userService.findByEmail(emailTest).join();
assertThat(user).isEqualTo(fakeUser);
verify(mockUserRepository).findByEmail(emailTest);
}
}
When I run this test, I got a MockitoException:
org.mockito.exceptions.base.MockitoException:
Cannot instantiate #InjectMocks field named 'userService'.
...
Caused by: org.mockito.exceptions.base.MockitoException: the type 'UserService' is an interface.
Instead of using the interface, I tried to use the real implementation; changing the test like this:
#InjectMocks
UserServiceImpl userService;
Now, the test passes with success, but this don't appear be right (at least for me).
I like to test the UserService that Spring Boot is using (suppose that in a new version of my system, I implement a new UserServicePostgreSQLImpl - now I'm using MongoDB).
(edit: see the bottom edit in the question)
I changed the Unit Test as follows:
#Autowired
#InjectMocks
UserService userService;
but now I got a test failure:
Expected :model.User#383caf89
Actual :null
For some reason, when I use #Autowired, the UserRepository mock doesn't work.
If I change the emailTest to use a real email in my database,
the test passes.
When I use #Autowired,
the test is using the real UserRepository and not a Mock version of UserRepository.
Any help?
Edit: looking at the answers from #msfoster and #dunni, and thinking better, I believe that the correct way is to test every implementation (in my example, use UserServiceImpl userService).
In order for your UserServiceImpl to be autowired when annotating it with #InjectMocks then it needs to registered as a Spring bean itself. You can do this most simply by annotating your UserServiceImpl class with #Service.
This will ensure it is picked up by the component scan in your Spring boot configuration. (As long as the scan includes the package your service class is in!)
You are running your tests with SpringRunner but for mocks you don't really need spring context. Try following code
// Using mockito runner
#RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
#Mock
UserRepository mockUserRepository;
// Mockito will auto inject mockUserRepository mock to userService via constructor injection
#InjectMocks
UserService userService;
#Test
public void mustReturnUser() throws InterruptedException {
String emailTest = "foo#bar.com";
User fakeUser = new User();
fakeUser.setEmail(emailTest);
when(mockUserRepository.findByEmail(emailTest)).thenReturn(fakeUser);
User user = userService.findByEmail(emailTest).join();
assertThat(user).isEqualTo(fakeUser);
verify(mockUserRepository).findByEmail(emailTest);
}
}
This is just a variation on the #Yogesh Badke answer.
Although you are using spring at runtime,
there is no need to use spring during the unit test.
Instead,
you can mock all the dependencies and set them to the mocks during test setup
(using reflection or setters, if you have them).
Here is some example code:
import org.springframework.test.util.ReflectionTestUtils;
public class TestUserService
{
private static final String VALUE_EMAIL = "test email value";
private UserService classToTest;
#Mock
private User mockUser;
#Mock
private UserRepository mockUserRepository;
#Before
public void beforeTest()
{
MockitoAnnotations.initMock(this);
classToTest = new UserService();
doReturn(mockUser).when(mockUserRepository).findByEmail(VALUE_EMAIL);
ReflectionTestUtils.setField(
classToTest,
"userRepository",
mockUserRepository);
}
#Test
public void findByEmail_goodEmailInput_returnsCorrectUser()
{
final User actualResult;
actualResult = classToTest.findByEmail(VALUE_EMAIL);
assertSame(
mockUser,
actualResult);
}
}
If interface is implemented by more than one class, then use the qualifier name (example below) in Junit beans.xml file to run the respective Junit test case.
Example:
#Autowired
#Qualifier("animal")
private Animal animals;
In Junit beans.xml
<bean id="animal" class="com.example.abc.Lion"/>
where Lion is the implementation class for the Interface Animal.
You need to #InjectMocks for the implementation class. Not the interface class.
Example:
#RunWith(SpringRunner.class)
#SpringBootTest
public class UserServiceTest {
#Mock
UserRepository mockUserRepository;
#InjectMocks
UserServiceImpl userServiceImpl; ------> This is important
}

Not able to use #Autowired with #Mock while creating unit test cases

I have a service say MainService and it has few managers which are initialised with #Autowired, and it is using some external service which are also #Autowired.
My purpose is to create unit test cases so that I can access the inmemory DB with managers, and want to mock the external service.
Now problem which I am facing is if I use #Autowired in my unit test and use #Mock for external services, then it doesn't use mock methods, Instead it uses the actual implementation. If I do #InjectMocks then it doesn't pick the data from repo as it doesn't find the respective dependencies for managers, and if I use #Autowired and #InjectMocks together it still not being able to use the Mocks.
Something like this
#Service
public class MainService extends AbstractService
{
#Autowired
Manager1 manager1;
#Autowired
Manager2 manager2;
#Autowired
Manager3 manager3;
#Trace(dispatcher = true)
public void mainMethod(int data)
{
int data1 = manager1.getData(int xyz);\\ getting data from DAO
int data2 = manager1.getData(int xyz);\\ getting data from DAO
int data3 = manager1.getData(int xyz);\\ getting data from
\\External Service
}
}
Now the test case I am writing is
#RunWith(SpringRunner.class)
#ActiveProfiles("test")
public class TestClass {
#InjectMocks
#Autowired
MainService service;
#Autowired
RepoForManager1 repoManager1;
#Autowired
RepoForManager2 repoManager2;
#Mock
Manager3 manager3;
#Before
public void initTest()
{
MockitoAnnotations.initMocks(this);
int dataFirst=1;
int dataSecond =2;
int dataThird=3;
int dataForMethod=4;
repoManager1.save(dataFirst);
repoManager2.save(dataSecond);
}
#Test
public void testMethod()
{
Mockito.when(manager3.getData(Mockito.anyInt())).thenReturn(dataThird);
service.mainMethod(dataForMethod);
}
}
This is a replication of the actual service, when I debug the test I found that the mock is not being used, its using actual implementation, and when I removed #Autowired from MainService then it only execute the mocked method.
Instead of using Field Injection(using #Autowired on class variables) use Constructor Injection. This way you can initialize your MainService class with some mocks and some real Implementations. Something like this
#Service
public class MainService extends AbstractService
{
private final Manager1 manager1;
private final Manager2 manager2;
private final SomeExternalService externalService;
#Autowired
public MainService(Manager1 manager1, Manager2 manager2, SomeExternalService externalService)
this.manager1= manager1;
this.manager2= manager2;
this.externalService = externalService;
}
........................
}
From your test class don't Autowire MainService. Just Autowire Manager1 and Manager2 and Create Mock for SomeExternalService(and initialize it). And then create instance of MainService using constructor.
public class TestClass {
MainService service;
#Autowired
RepoForManager1 repoManager1;
#Autowired
RepoForManager2 repoManager2;
#Mock
SomeExternalService externalService;
#Before
public void setUp(){
service = new MainService(repoManager1, repoManager2, externalService);
}
}
If you want to use Mockito, you need Annotate your TestClass with #RunWith(MockitoJUnitRunner.class) instead of #RunWith(SpringRunner.class).
Then for the #AutoWired in the TestClass for repoManager1, repoManger2. Annotate them with #Mock instead of #Autowired, as you want to mock them with Mockito.
I have not used the SpringRunner myself, but from a quick read, I can see that it is used mostly for Integration Test, where you want to load the SpringContext..etc.
Also #ActiveProfiles("test") is mostly used for Integration Test, where you want to load the spring context, with the 'test' profile properties.

Junit for Spring Cache Manager

I am using spring caching layer in my application and I have got an issue as part of writing unit tests to test spring cache layer using Mockito.
Please refer the below code for my problem:
Service layer:
public CustomerServiceImpl implements CustomerService {
#Autowired
private CacheManager cacheManager;
#Autowired
private CustomerRepository customerRepository;//Repository is simple JPA repository interface which contains findByCustomerName()
#Override
#CachePut(value = "#customer", key = "#customer.customerName")
public Customer insertOrUpdate(Customer customer) {
return customerRepository.save(customer);
}
#Cacheable(value="customersCache", key = "#customerName")
public Customer findByCustomerName(String customerName) {
Customer customer = customerRepository.findByCustomerName(customerName);
return customer;
}
}
JUnit test Code for Service layer:
#RunWith(PowerMockRunner.class)
#PrepareForTest(CustomerServiceImplTest.class)
public class CustomerServiceImplTest {
#Spy
CacheManager cacheManager = new ConcurrentMapCacheManager("customersCache");
#Mock
CustomerRepository CustomerRepository;
#InjectMocks
CustomerServiceImpl customerServiceImpl = new CustomerServiceImpl();
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testCacheForFindByCustomerName() {
Customer customer1 = new Customer();
customer1.setId("1");
customer1.setName("John");
Customer customer2 = new Customer();
customer2.setId("2");
customer2.setName("Sara");
//this should save to cache
Mockito.when(CustomerRepository.save(customer1))
.thenReturn(customer1);
customerServiceImpl.insertOrUpdate(customer1);
//Now it should retreive from cache, but not able to
Mockito.when(CustomerRepository.findByCustomerName(Mockito.any(String.class)))
.thenReturn(customer1, customer2);
Customer result = customerServiceImpl.findByCustomerName("John");
assertThat(result, is(customer1));
result = customerServiceImpl.findByCustomerName("John");
assertThat(result, is(customer1));
}
}
Exception:
I am getting an "java.lang.AssertionError:" because the caching layer did not work and the call has been passed to repository object (twice) which has returned the 'customer2' mock object above i.e., the repository method has been invoked twice for the same key by passing the service layer.
Also, please note that I am using "Mockito" framework for my tests.
I have tried to google for unit tests on spring caching and also referred the below URL, which is almost using the same concept, but it does not work for my above code.
How to test Spring's declarative caching support on Spring Data repositories?
Could you please help to resolve the above exception ?
The Spring Cache Manager relies on Spring managing the application. You can't get that with the PowerMockRunner, you need to use SpringJUnit4Runner. You can still use PowerMock or Mockito programmatically, but not as a Runner.
Typically, you'll turn your unit test into a Spring-style integration test, something like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class SpringTest {
#Configuration
#EnableCaching
static class SpringConfig{
#Bean
public CustomerService customerService(){
return new CustomerServiceImpl(customerRepository());
}
#Bean
public CustomerRepository customerRepository(){
return Mockito.mock(CustomerRepository.class);
}
}
#Autowired
CustomerService customerService; // this will contain a proper managed cache
#Autowired
CustomerRepository customerRepository; // this is a mockito mock you can fine-tune
}

How to mock a private dao variable?

I have a dao.create() call that I want to mock when testing a method.
But I am missing something as I'm still getting NPE. What is wrong here?
class MyService {
#Inject
private Dao dao;
public void myMethod() {
//..
dao.create(object);
//
}
}
How can I mock out the dao.create() call?
#RunWith(PowerMockRunner.class)
#PrepareForTest(DAO.class)
public void MyServiceTest {
#Test
public void testMyMethod() {
PowerMockito.mock(DAO.class);
MyService service = new MyService();
service.myMethod(); //NPE for dao.create()
}
}
You are not injecting the DAO. With mockito you can change your test class to use #InjectMocks and use mockito runner.
#RunWith(MockitoJUnitRunner.class)
public void MyServiceTest {
#Mock
private Dao dao;
#InjectMocks
private MyService myService;
...
}
You can read more about InjectMocks at Inject Mocks API
Simpler way is changing your injection to injection by constructor. For example, you would change MyService to
class MyService {
...
private final Dao dao;
#Inject
public MyService(Dao dao) {
this.dao = dao;
}
...
}
then your test you could simple pass the mocked DAO in setup.
...
#Mock
private Dao dao;
#Before
public void setUp() {
this.dao = mock(Dao.class);
this.service = new MyService(dao);
}
...
now you can use verify to check if create was called, like:
...
verify(dao).create(argThat(isExpectedObjectBeingCreated(object)));
}
private Matcher<?> isExpectedObjectBeingCreated(Object object) { ... }
Using injection by constructor will let your dependencies clearer to other developers and it will help when creating tests :)
You still need to set the dao field with your mock.
You can use reflection to this.
You need to inject/set the mocked object DAO in your service class.
If it is a spring based project, you may have a look # Spring Junit Testrunner
If you use new MyService() the Dao is never injected. For the Dao to be injected you need to load the MyService via an ApplicationContext (Spring) or an Injector (Guice). Like you would in your normal application.
As others have already said, you need to set the dao field in your MyService class in some fashion. I'm unsure the mechanism to allow for a compound runner on your test to use both Powermock and a DI framework runner (assuming Powermock is required), but as long as you're already using PowerMock (for reasons unclear in the given example), you could avail yourself of the Whitebox class to set the dao more manually.
public void testMyMethod() {
Dao dao = mock(Dao.class)
doNothing().when(dao).create(anyObject())); //assuming no return val for dao.create()
MyService service = new MyService();
Whitebox.setInternalState(service, "dao", dao);
service.myMethod();
}

Categories