Problem with #Cachable and #Mock/#InjectMocks - java

I'm currently unable to use #Cacheble and #Mock/#InjectMocks in the same test class.
To be clear, for using the Mockito.verify(repository, times(1)) I need to #Mock the repository and use the annotation #InjectMock for the repository. Doing it, I can correctly verify the behave of my app, but the result of findById is not cached. In fact the manager.get(cacheName).get(key) will return null.
Instead, if i use the #Autowired annotation, the value is cached but the verify(repository, times(1)) return ZeroInteractions. I checked with the debugger and the behaviour is ok.
What i should do for have both the cache-store and the verify() working? Why is that happening?
[I'm using SpringBoot 2.1.3-release with Caffeine Cache Manager version 3.1.1]
This is the CacheManager class:
#Slf4j
#Configuration
#EnableCaching
public class CaffeineCacheConfig extends CachingConfigurerSupport {
#Bean("cacheManager")
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(generateCache());
return cacheManager;
}
}
This the Cache:
#Override
public CaffeineCache generateCache() {
return new CaffeineCache(name,
Caffeine.newBuilder()
.maximumSize(15)
.expireAfterWrite(60, TimeUnit.MINUTES)
.recordStats()
.build());
}
I have a service that im trying to cache which is like:
#Service
#CacheConfig(cacheNames = CacheLocations.APPLICATION_LEVEL)
#Transactional(readOnly = true)
public class ApplicationLevelService implements IApplicationLevelService
{
#Autowired
private ApplicationLevelRepository repository;
#Autowired
private CacheManager manager;
#Override
#Cacheable
public ApplicationLevel findById(int id)
{
return repository.findById(id);
}
}
Im trying to test it with this class (JUnit5):
#CacheConfig(cacheNames = {APPLICATION_LEVEL})
class ApplicationLevelCacheTest extends AbstractSpringTest
{
#InjectMocks
private ApplicationLevelService service;
#Mock
private ApplicationLevelRepository repository;
#Autowired
private CacheManager cacheManager;
#BeforeEach
void evictCache()
{
cacheManager.getCache(APPLICATION_LEVEL).clear();
}
#Nested
class TestApplicationLevelCache
{
#Test
#DisplayName("findAById() sets the resulting list in '" + CacheLocations.APPLICATION_LEVEL + "' cache")
void testApplicationLevelCaching_ApplicationLevelsAreCached_FindById()
{
when(repository.findById(anyInt())).thenReturn(Optional.of(new ApplicationLevel("mocked")));
assertNotNull(cacheManager.getCache(CacheLocations.APPLICATION_LEVEL));
var expected = service.findById(1);
verify(repository, times(1)).findById(anyInt());
assertNotNull(cacheManager.getCache(APPLICATION_LEVEL).get(1));
// should be cached
var actual = service.findById(1);
verifyZeroInteractions(repository);
assertEquals(expected, actual);
}
}
}
where AbstractSpringTest is just the class containing:
#SpringJUnitConfig
#SpringBootTest()
#ActiveProfiles("test")

Testing #Cacheable requires #SpringBootTest to bootstrap the entire container. This means this is an integration test you wrote, going through many layers of the application.
In this case, prefer #Autowired/#MockBean to #InjectMock/#Mock (you can use keep #InjectMock/#Mock for unit tests).
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html
Try replacing:
#InjectMocks ApplicationLevelService service;
#Autowired CacheManager cacheManager;
#Mock ApplicationLevelRepository repository;
With:
#Autowired ApplicationLevelService service;
#Autowired CacheManager cacheManager;
#MockBean ApplicationLevelRepository repository;

The verify() method can be used to verify the number of interactions of a bean that has been mocked with Mockito.
Using the #Autowired annotation you have initialized the bean by taking it directly from the Spring container, using Spring's D.I.
Your cacheManager object is outside the context of Mockito.
It has not been mocked with Mockito.
I think you can solve this problem by using the #Spy annotation on the cacheManager object.
#Spy and #Mock can be both used to mock fields.
The difference is that with the #Mock annotation, you create a complete mock or dummy object, whereas with #Spy, you have the real object and can directly call or mock specific methods of it.
You can try with:
#Spy
CacheManager cacheManager = new SimpleCacheManager();
Hope this help.

Related

Init spring boot mocks before they are injected

Is there a way to initialise mocks before they are injected into another component?
To give an example I have following classes:
#Service
SomeService {
#Autowired
public SomeService(SomeConfig config)
}
#Configuration
#Getter
#Setter
public class SomeConfig {
private String someValue;
}
In my test I'm doing the following:
#MockBean
SomeConfig someConfig;
#Autowired
SomeService someService;
The problem is, that the SomeService constructor is already accessing SomeConfig members, before I can even initialise it with when(someConfig.getSomeValue()).thenReturn("something"), resulting in a NullPtrException.
Is there a hook that gets executed before SomeService is instantiated?
You could manually setup your Service in a setup method.
Just make sure to exclude your SomeService from classpath scanning in those tests.
SomeServiceTest {
#MockBean
SomeConfig someConfig;
SomeService someService;
#BeforeEach
public void setup(){
// init mocks
// setup other stuff
someService = new SomeService(someConfig);
}
}
I assume that the SomeService looks like:
#Service
SomeService {
private final SomeConfig config
#Autowired
public SomeService(SomeConfig config) {
this.someConfig = someConfig;
// some logic with config getters
}
}
You should use annotation
#PostConsturct in a separate method and put the usage of config getters there, like:
#PostConstruct
private void postConstruct() {
// some logic with config getters
}

#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

Define common mock objects for many test classes

I'm learning unit testing with Spring Boot. I've created a stub object from Spring Data repository class using Mockito. All works fine, here's a code:
#SpringBootTest
class UserServiceTest {
#MockBean
private UserRepository userRepository;
#BeforeAll
public void configureMock() {
User user = new User("mishenev",
"Dmitrii",
"Mishenev",
"123",
"mishenev.8#gmailcom",
new UserSettings());
when(userRepository.findById(anyLong())).thenReturn(Optional.of(user));
when(userRepository.findUserByUserName(anyString())).thenReturn(user);
// .. Tests
}
But it's kind of boilerplate style to use this #BeforeAll test repository configuration for each class, if we don't need different stub behaviour. Then i tried to use #TestConfiguration
#TestConfiguration
public class SpringRepositoryTestConfiguration {
#Bean
public UserRepository userRepository () {
UserRepository userRepository = Mockito.mock(UserRepository.class);
// Configuring mock's behaviour
return userRepository;
}
}
After that i used
#SpringBootTest(classes = SpringRepositoryTestConfiguration.class)
But i can't using #MockBean to autowire repository in UserService now.
I want to understand is it possible to take out and use all test Repository type stubs in one configuration class. Thank you in advance!
Just use this
class TestConfig {
#Bean
UserRepository userRepository() {
final UserRepository repo = mock(UserRepository.class);
.. do mocking ..
return repo;
}
}
Then you can just #Import(TestConfig.class) where you need it

Why is the constructor method being called before setup

Here is my class under test:
KafkaProcessorApplication
#EnableBinding(Processor.class)
#EnableConfigurationProperties(KafkaProperties.class)
public class KafkaProcessorApplication {
#Autowired
private Processor processor;
#Autowired
private KafkaProperties kafkaProperties;
private KafkaTemplate<String, String> kafkaTemplate;
#Autowired
KafkaProcessorApplication(SenderConfig senderConfig) {
this.kafkaTemplate = senderConfig.kafkaTemplate();
}
Here, SenderConfig is a just a simple config class with the method kafkaTemplate() creating a new instance of KafkaTemplate.
SenderConfig
#Configuration
public class SenderConfig {
#Autowired
KafkaProperties kafkaProperties;
public ProducerFactory<String, String> producerFactory() {
return new DefaultKafkaProducerFactory<>(new HashMap());
}
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(ProducerFactory()));
}
}
Here is the test class:
KafkaTestClass
#SpringBootTest
#ActiveProfiles("test")
#ContextConfiguration(classes = {SenderConfig.class, KafkaProcessorApplication.class})
#TestPropertySource(locations = "classpath:test-resources.properties")
#RunWith(SpringRunner.class)
public class KafkaProcessorApplicationTest {
#Autowired
private Processor processor;
#Mock
private SenderConfig senderConfig;
#Mock
private KafkaProperties kafkaProperties = new KafkaProperties();
#Mock private KafkaTemplate mockKafka;
#Autowired
#InjectMocks
private KafkaProcessorApplication app;
#Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
doReturn("ServerConfig").when(kafkaProperties).getServersConfig();
when(senderConfig.kafkaTemplate()).thenReturn(kafkaTemplate);
}
I want to mock kafkaTemplate. But, its instantiation is in constructor which is being executed even before the #Before is executed, where the logic of mocking the method is written.
Just curious why is the constructor being executed first, and also, how can I mock the method if this is the case?
What could be the approaches of mocking the kafkaTemplate, without using Powermock and without modifying the class under test as I can not change it?
When you use #SpringBootTest, the Spring dependency tree is resolved before the #Before method has a chance to execute. This includes constructing the KafkaProcessorApplication bean and its dependencies. This is why the constructor runs before #Before.
What you want is Spring's #MockBean to create and inject a mock bean in the application context.
This question has a great write-up of how you can use this: Difference between #Mock, #MockBean and Mockito.mock()
update
Now I see. The problem is that the KafkaProcessorApplication accesses the mock in its constructor before you can configure it.
This can be solved with a separate test Spring configuration that will return a configured SenderConfig mock bean as described here: Testing spring bean with post construct

Mocking #Resource when using MockMvcBuilders.standaloneSetup

My team is currently building a SpringBoot API. Below is a snippet of how my team is mocking for our controller tests. This works when we have plain classes as dependencies.
#RunWith(SpringRunner.class)
#SpringBootTest
public class TestController {
private MockMvc mockMvc;
#Mock
private MyService myService;
#Autowired
#InjectMocks
private MyController myController;
#Before
public void setup() {
//Build the controller mock handler
mockMvc = MockMvcBuilders
.standaloneSetup(myController)
.build();
}
We cannot figure out how to mock a #Resource like below for a similar test.
#Resource(name = "domainNameToCode")
private Map<String, Integer> domainCodes;
As the domainCodes is private your controller needs to have a way to set value for it either constructor or a setter.
Setter
#Autowired()
#Qualifier("domainNameToCode")
public void setDomainCodes(Map<String, Integer> domainCodes) {
...
}
Constructor
#Autowired()
#Qualifier("domainNameToCode")
public MyController(Map<String, Integer> domainCodes){
}
In your tests, now you can inject the mocks.
If you don't want to edit your source, then you can use reflection to set a private instance variable on an object
//Using reflection to set a private field on an object
FieldUtils.writeField(controllerInstance, "domainCodes", new HashMap<>()) ;

Categories

Resources