Junit for Spring Cache Manager - java

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
}

Related

Testing repositories which do not extend any Spring Data Repository

I am writing unit and integration tests for my app and I have a problem with testing repositories.
This is very simple repo for handling authorities:
public interface AuthorityRepository {
Authority saveAuthority (Authority authority);
}
#Repository
public class AuthorityRepositoryImpl implements AuthorityRepository {
#PersistenceContext
EntityManager entityManager;
#Autowired
public AuthorityRepositoryImpl(EntityManager entityManager) {
this.entityManager = entityManager;
}
#Override
#Transactional
public Authority saveAuthority(Authority authority) {
entityManager.persist(authority);
return authority;
}
}
I have two questions regarding this code:
Is it wrong approach to not extend any Spring Data Repository interface? Besides not having to write all methods to communicate with database what are other advantages of using it?
How can I test this repository using minimum resources? #DataJpaTest does not work, because (from my understanding) it picks up repos that extend any Spring Data Repository. This works:
#RunWith(SpringRunner.class)
#SpringBootTest
public class AuthorityRepositoryTest {
#Autowired
private AuthorityRepository authorityRepository;
#PersistenceContext
private EntityManager entityManager;
#Test
public void test () {
Authority authority = new Authority();
authority.setName("name");
authority.setUsername("username");
authorityRepository.saveAuthority(authority);
assertNotNull(entityManager.find(Authority.class, 1));
}
But #SpringBootTest is pretty slow as it creates whole application context and I would like to make these tests faster.
I am using H2 database both in main and test, which is declared in application.properties file.
#DataJpaTest works when coupled with #Import(repo.class). It looks like this:
#RunWith(SpringRunner.class)
#DataJpaTest ()
#Import(AuthorityRepositoryImpl.class)
public class AuthorityRepositoryTest2 {
#Autowired
AuthorityRepository authorityRepository;
#Autowired
TestEntityManager testEntityManager;
#Test
public void test() {
Authority authority = new Authority();
authority.setUsername("name");
authority.setUsername("username");
authorityRepository.saveAuthority(authority);
assertNotNull(testEntityManager.find(Authority.class, 1));
}
I learned though, that if I do integration test I could aswell create whole application context and test all things I want reusing this one context, so later when I write more integration tests for other parts of my app I am going to end up using #SpringBootTest for all of these tests anyway.
Thanks for comments, helped me to understand integration tests better.

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.

Spring Boot, Mockito, injecting mock into scope session bean

I'm having a problem injecting mock into one class I need for testing. I'm trying to mock a Dao class and had no problem doing so using ReflectionTestUtils in various services I'm using, however this one just does not want to work, it keeps calling the Dao class and getting errors from the database.
This is the test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#WebAppConfiguration
public class DedicationControllerTest extends AbstractRestTest {
#Mock
UserDaoImpl userDao;
#Autowired
#InjectMocks
GrantedAuthoritiesLevelsHolder grantedAuthoritiesLevelsHolder;
#Test
public void shouldTest() throws Exception {
//given
String json = this.getJsonFromFile("json/my.json");
Mockito.when(userDao.getUser(Mockito.anyString())).thenReturn(new User(1l, "mock"));
ReflectionTestUtils.setField(grantedAuthoritiesLevelsHolder, "userDao", userDao);
ResultActions result = mockMvc.perform(post( controllerUrl + "/action")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(json));
// then
result
.andExpect(status().isOk());
}
}
And this is the class I'm trying to inject mock into:
#Component
#Scope(value="session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class GrantedAuthoritiesLevelsHolder {
#Autowired
private UserDao userDao;
// some methods
}
You will have to register mocked bean as UserDao when the context is getting loaded. You can register it as shown below. Put this in any class annotated with #Configuration
#Bean
#Primary
public UserDao UserDao() {
return mock(UserDao.class);
}
I believe that your configuration may be not enough to put a mock into Spring context.
My advice:
#MockBean(answer=Answers.RETURNS_SMART_NULLS)
UserDao userDao;
#Autowired
GrantedAuthoritiesLevelsHolder grantedAuthoritiesLevelsHolder;
It should put a mock into Spring context, moreover it should give you hints with incorrect/missing stubbing.

Own mock of JPA repository in Spring Boot

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?

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
}

Categories