Java / Spring: Cannot test Caching in Unit Test - java

In my Java application I am trying to test the following service method with a Unit Test:
#Service
#EnableCaching
#RequiredArgsConstructor
public class LabelServiceImpl implements LabelService {
private static final String CACHE_NAME = "demoCache";
private final LabelRepository labelRepository;
#Override
public List<LabelDTO> findByUuid(UUID uuid) {
final Label label = labelRepository.findByUuid(uuid)
.orElseThrow(() -> new EntityNotFoundException("Not found."));
final List<LabelDTO> labelList = labelRepository.findByUuid(uuid);
return labelList;
}
}
Here is my Unit Test:
#Import({CacheConfig.class, LabelServiceImpl.class})
#ExtendWith(SpringExtension.class)
#EnableCaching
#ImportAutoConfiguration(classes = {
CacheAutoConfiguration.class,
RedisAutoConfiguration.class
})
#RunWith(MockitoJUnitRunner.class)
public class CachingTest {
#InjectMocks
private LabelServiceImpl labelService;
#Autowired
private CacheManager cacheManager;
#Mock
private LabelRepository labelRepository;
#Test
void givenRedisCaching_whenFindUuid_thenReturnFromCache() {
//code omitted
LabelDTO labelFromDb = labelService.findByUuid(uuid);
LabelDTO labelFromCache = labelService.findByUuid(uuid);
verify(labelRepository, times(1)).findByUuid(uuid);
}
}
When I use #Autowired for LabelServiceImpl in the Unit Test, I get "Nullpointer exception" error. If I use #InjectMocks annotation for that instance, then there is no error, but no caching is done (it calls labelService.findByUuid 2 times instead of 1).
I think I made a mistake related to the annotations in Unit Test class, but I tried many different combinations and cannot solve the problem.
So, how can I fix the problem?
Update: Finally I fixed the problem by using the following approach. However, I also had to update the mockito-core version from 3.3.3 to 3.7.0 as shown below. Otherwise I was getting "java.lang.NoSuchMethodError: org.mockito.MockitoAnnotations.openMocks(Ljava/lang/Object;)Ljava/lang/AutoCloseable;" error.
pom.xml:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.7.0</version>
<scope>test</scope>
</dependency>
CachingTest:
#ExtendWith(MockitoExtension.class)
#SpringBootTest
public class CachingTest {
#Autowired
#InjectMocks
private LabelServiceImpl labelService;
#MockBean
private LabelRepository labelRepository;
#Test
public void givenRedisCaching_whenFindUuid_thenReturnFromCache() {
//code omitted
LabelDTO labelFromDb = labelService.findByUuid(uuid);
LabelDTO labelFromCache = labelService.findByUuid(uuid);
verify(labelRepository, times(1)).findByUuid(uuid);
assertEquals(labelFromDb, labelFromCache);
}
}

Mockito does not simulate your springt boot application. It just mocks the annotated objects and injects your mock objects into your labelService.
On the other hand to test a spring boot application #Autowired is not enough.
You have to build a Spring Boot Integration Test.
To do this annotate your Test with #SpringBootTest.
This initializes a real Application Context.
try to autowire, LabelService
package com.demo;
import org.springframework.stereotype.Service;
#Service
public class LabelService
{
public String getLabel()
{
return "Test Label " + System.currentTimeMillis();
}
}
package com.demo;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
#SpringBootTest
public class CachingTest
{
#Autowired
private LabelService labelService;
#Test
public void testLabelService()
{
assertNotNull(labelService);
}
}
this works

Related

Configuration Getting Ignored in Test

I try to test my spring app but encounter following problem:
In "normal mode"(mvn spring-boot:run) the app starts as expected and adapterConfig gets set and is NOT NULL. When I start my testclass to test the MVC, adapterConfig does not get set. Spring ignores the whole config class.
test:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = StudentController.class)
public class StudentControllerTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private StudentService service;
#MockBean
private StudentRepository repository;
#Test
public void shouldReturnABC() throws Exception{
MvcResult result = this.mockMvc.perform(get("/students/abc")).andReturn();
}
}
controller:
#RestController
#RequestMapping("/students")
#PermitAll
public class StudentController {
#Autowired
StudentService studentService;
//get
#GetMapping("/abc")
public String abc (){
return "abc";
}
config:
#Configuration
public class SpringBootKeycloakConfigResolver implements KeycloakConfigResolver {
private KeycloakDeployment keycloakDeployment;
private AdapterConfig adapterConfig;
#Autowired
public SpringBootKeycloakConfigResolver(AdapterConfig adapterConfig) {
this.adapterConfig = adapterConfig;
}
#Override
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
if (keycloakDeployment != null) {
return keycloakDeployment;
}
keycloakDeployment = KeycloakDeploymentBuilder.build(adapterConfig);
return keycloakDeployment;
}
}
adapterConfig is null when hitting the test but gets set & created when hitting it the normal way, any idea?
Using #WebMvcTest, the container will inject only components related to Spring MVC (#Controller, #ControllerAdvice, etc.) not the full configuration use #SpringBootTest with #AutoConfigureMockMvc instead.
Spring Boot Javadoc
Keycloak's AutoConfiguration is not included by #WebMvcTest.
You could
Include it manually via #Import(org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration.class)
Or use #SpringBootTest
with spring boot 2.5 i had I had to import KeycloakAutoConfiguration into my test.
#WebMvcTest(value = ApplicationController.class, properties = "spring.profiles.active:test")
#Import(KeycloakAutoConfiguration.class)
public class WebLayerTest {
// ... test code ....
}

Spring #Autowired field is null when running Mockito test [duplicate]

This question already has answers here:
Why is my Spring #Autowired field null?
(21 answers)
Closed 4 years ago.
This is my main code:
#Service
public class MainService {
public String mainMethod() {
SomeService someService = new SomeService("required");
// do Sth.
}
}
#Service
#RequiredArgsConstructor
public class SomeService {
#Autowired
private SomeOtherService someOtherService;
#Notnull
private final String requiredField;
// ...
}
#Service
public class SomeOtherService {
// just a bunch of public methods that might as well be static
}
And this is the test setup:
#RunWith(SpringRunner.class)
public class MainServiceTest {
#InjectMocks
private MainService mainService;
#Test
public void givenSth_whenSth_doSth() {
// test mainService.mainMethod();
}
}
Can you please tell me why someOtherService inside SomeService is null?
I figured out when yo use Spring's injection, you should not use manual instantiation (new ...) at the same time; so maybe new SomeService("required") is the problem? But then how do I inject field variables into SomeService if not by constructor call? I don't want to use the Builder because requiredField is supposed to be NotNull.
You can follow Adam Weidner's tutorial using MockBean for Spring Bean and extending AbstractTestNGSpringContextTest for TestNG with Spring
#TestExecutionListeners(MockitoTestExecutionListener.class)
#SpringBootTest
public class WidgetServiceIntegrationTest extends AbstractTestNGSpringContextTest {
#MockBean private SprockService sprockService;
#MockBean private SpringService springService;
#Autowired WidgetService widgetService;
#Test
public void testSomethingOnWidgetService() {
when(sprockService.makeASprock()).thenReturn(...);
For integration testing consider instantiating bean using context configuration like below
#RunWith(SpringRunner.class)
#ContextConfiguration("mockito-config.xml")
For a detailed explanation, please refer
https://blog.jayway.com/2011/11/30/spring-integration-tests-part-i-creating-mock-objects/
For unit testing, Please consider using #Mock annotation provided by Mockito.
In this specific example, We could mock SomeOtherService as shown below
#RunWith(SpringRunner.class)
public class MainServiceTest {
#InjectMocks
private MainService mainService;
#Mock
private SomeOtherService someOtherService
#Test
public void givenSth_whenSth_doSth() {
// test mainService.mainMethod();
}
}
For a detailed explanation please refer the following post
http://www.vogella.com/tutorials/Mockito/article.html
http://static.javadoc.io/org.mockito/mockito-core/2.23.0/org/mockito/MockitoAnnotations.html

How to run Spring unit tests with a different JUnit Runner?

I'm testing an event processor in multithread. So I use the concurrent-junit of vmlens in my test case.
But I got nullpoint exceptions when I autowired beans since I was using ConcurrentTestRunner instead of SpringJunit4ClassRunner.
This is my pom
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.vmlens</groupId>
<artifactId>concurrent-junit</artifactId>
<version>1.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>2.6.0</version>
<scope>test</scope>
</dependency>
Test case source code:
import com.anarsoft.vmlens.concurrent.junit.ConcurrentTestRunner;
import com.anarsoft.vmlens.concurrent.junit.ThreadCount;
#RunWith(ConcurrentTestRunner.class)
#ContextConfiguration(locations = "classpath:applicationContext.xml")
public class EventListenerTest {
#Autowired
private EventStore es; //defined in applicationContext.xml
#Autowired
private EntityAppender ea; //a #Component
......
#Test
#ThreadCount(10)
public final void testDefaultListener() {
Long bef = es.countStoredEvents();// nullpoint exception
TestEntity1 t1 = ea.appWithDefaultListener();// nullpoint exception
......
}
}
Obviously, beans were not injected correctly.
Is there any way to fix this? Should I extend AbstractJUnit4SpringContextTests?
Attached latest code here:
EventStore is a Jpa repository:
public interface EventStore extends JpaRepository<DomainEvent, Long>{};
applicationContext.xml
<aop:config proxy-target-class="true" />
<context:annotation-config />
<jpa:repositories base-package="com.my"></jpa:repositories>
EntityAppender is defined just for test.
#Component
public class EntityAppender {
#Autowired
private TestEntity1Repository myRepository; //another Jpa repository
public EntityAppender() {
super();
}
#Transactional
public TestEntity1 appWithDefaultListener() {
TestEntity1 t1 = new TestEntity1(UUID.randomUUID().toString().replaceAll("-", ""), "aaaaaaaaaaaa", 44,
LocalDate.now());
return myRepository.save(t1);
}
...
}
Test case:
import com.anarsoft.vmlens.concurrent.junit.ConcurrentTestRunner;
import com.anarsoft.vmlens.concurrent.junit.ThreadCount;
#RunWith(ConcurrentTestRunner.class)
#ContextConfiguration(locations = "classpath:applicationContext.xml")
public class EventListenerTest {
#ClassRule
public static final SpringClassRule springClassRule = new SpringClassRule();
#Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
#Autowired
private EventStore es;
#Autowired
private EntityAppender ea;
......
#Before
public void setUp() throws Exception {
bef = es.count(); //<====nullpoint exception due to es being null here
}
#Test
#ThreadCount(10)
public final void testDefaultListener() {
bef = es.count(); //<====== es worked well here
TestEntity1 t1 = ea.appWithDefaultListener();
......
}
}
Since Spring 4.2, when you need to provide your own JUnit Runner for some other reason (eg. ConcurrentTestRunner or Mockito's MockitoJUnitRunner), Spring provides a separate mechanism to initialize the unit test's ApplicationContext and meta configuration.
This mechanism is a combination of two JUnit 4 rules. This is documented in the official documentation, here.
The org.springframework.test.context.junit4.rules package provides the
following JUnit 4 rules (supported on JUnit 4.12 or higher).
SpringClassRule
SpringMethodRule
SpringClassRule is a JUnit TestRule that supports class-level features
of the Spring TestContext Framework; whereas, SpringMethodRule is a
JUnit MethodRule that supports instance-level and method-level
features of the Spring TestContext Framework.
Your unit test class should minimally contain
#ClassRule
public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();
#Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
Here's a complete example that utilizes MockitoJUnitRunner while still making use of Spring's TestContext framework:
#ContextConfiguration(classes = ConfigExample.class)
#RunWith(MockitoJUnitRunner.class)
public class Example {
#ClassRule
public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();
#Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
#Autowired
private Foo foo;
#Mock
private Runnable runnable;
#Test
public void method2() {
Mockito.doNothing().when(runnable).run();
foo.bar(runnable);
Mockito.verify(runnable).run();
}
}
#Configuration
class ConfigExample {
#Bean
public Foo Foo() {
return new Foo();
}
}
class Foo {
public void bar(Runnable invoke) {
invoke.run();
}
}

How autowire a repository in an integration test with Spring Boot?

I'm trying to write an integration test but I'm having trouble with autowiring the repository in the test.
I receive this exception:
org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.observer.media.repository.ArticleRepository]: Specified class is an interface.
Edit:
I added PersistenceConfig.class with #EnableJpaRepositories, code below. This results in Exception org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'entityManagerFactory' available
I also tried to add Application.class in #SpringBootTest(classes = {} in an catch all attempt but that throws Error creating bean with name 'articleRepository': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Not a managed type: class org.observer.media.model.Article
ScraperRunnerIntegrationTest (the config classes only contain beans of domain classes):
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = {
ApplicationConfig.class,
PersistenceConfig.class,
DeMorgenTestConfig.class,
Article.class,
ScraperRunner.class,
DeMorgenWebsiteScraper.class,
ArticleService.class,
DeMorgenPageScraper.class,
JsoupParser.class,
DeMorgenArticleScraper.class,
GenericArticleScraper.class,
ImageMetaScraper.class,
ArticlePrinter.class,
ArticleRepository.class
})
public class ScraperRunnerIntegrationTest {
private final static Article EXPECTED_ARTICLE_1 = anArticle().withHeadline("headline1").build();
private final static Article EXPECTED_ARTICLE_2 = anArticle().withHeadline("headline2").build();
#Autowired
private ScraperRunner scraperRunner;
#Autowired
private DeMorgenWebsiteScraper deMorgenWebsiteScraper;
#Autowired
private ArticleRepository articleRepository;
#Test
public void run() {
scraperRunner.run(deMorgenWebsiteScraper);
assertThat(articleRepository.findAll()).containsOnly(EXPECTED_ARTICLE_1, EXPECTED_ARTICLE_2);
}
Repository:
import org.observer.media.model.Article;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
#Repository
public interface ArticleRepository extends JpaRepository<Article, Long> {
List<Article> findAll();
Article findByHash(String hash);
Article findByHeadline(String headline);
List<Article> findArticleByHeadlineAndCompanyName(String headline, String companyName);
#Query("SELECT CASE WHEN COUNT(a) > 0 THEN true ELSE false END FROM Article a WHERE a.hash = :hash")
boolean existsByHash(#Param("hash") String hash);
}
PersistenceConfig.class:
package org.observer.media.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
#Configuration
#EnableJpaRepositories("org.observer.media.repository")
public class PersistenceConfig {
}
You need to provide only the classes, annotated as #Configuration to #SpringBootTest.
I have modified the original example from here to use the #SpringBootTest annotation.
So the following configuration works:
#Configuration
#ComponentScan("hello")
public class AppConfig {
}
Note the #ComponentScan annotation.
And then in my test:
#RunWith(SpringRunner.class)
#SpringBootTest(classes={AppConfig.class})
public class CustomerRepositoryTests {
#Autowired
private CustomerRepository customerRepository;
}
And it did a trick. You can try to do the same in your example.
I had the same exception org.springframework.beans.BeanInstantiationException: Failed to instantiate [...]: Specified class is an interface on my integration test.
I had resolved without using a PersistenceConfig.class but changed only the annotaion used in my integration class that was wrong. I have used the following annotation:
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
because I didn't want use a in-memory database.
Your code could be the following:
#ComponentScan(basePackages = {"org.observer"})
#RunWith(SpringRunner.class)
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#DataJpaTest
public class ScraperRunnerIntegrationTest {
private final static Article EXPECTED_ARTICLE_1 = anArticle().withHeadline("headline1").build();
private final static Article EXPECTED_ARTICLE_2 = anArticle().withHeadline("headline2").build();
#Autowired
private ScraperRunner scraperRunner;
#Autowired
private DeMorgenWebsiteScraper deMorgenWebsiteScraper;
#Autowired
private ArticleRepository articleRepository;
#Test
public void run() {
scraperRunner.run(deMorgenWebsiteScraper);
assertThat(articleRepository.findAll()).containsOnly(EXPECTED_ARTICLE_1, EXPECTED_ARTICLE_2);
}
Maybe you can try #AutoConfigureDataJpa or #AutoConfigureXxx annotations.

Unit testing a AWS Spring Boot Configuration file - possible?

We are currently using Spring Boot to connect to a mocked local instance of Amazon SQS. The application itself is working when run, but we would like to try and test the SQS Config class, if possible and if it makes sense.
Here is the configuration class. All properties are pulled from the typical application.properties file when the Spring application itself is run.
import com.amazonaws.services.sqs.AmazonSQSAsync;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.aws.core.env.ResourceIdResolver;
import org.springframework.cloud.aws.messaging.core.QueueMessagingTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class AWSSQSConfig {
#Value("${aws.sqs.endpoint}")
private String AWSSqsEndpoint;
// Producer QueueMessageTemplate
#Bean
public QueueMessagingTemplate queueMessagingTemplate(AmazonSQSAsync amazonSqs, ResourceIdResolver resourceIdResolver) {
if (!AWSSqsEndpoint.isEmpty())
amazonSqs.setEndpoint(AWSSqsEndpoint);
return new QueueMessagingTemplate(amazonSqs, resourceIdResolver);
}
}
Here is the test class. We are attempting to pass the configuration in via TestPropertySource, but they don't actually seem to get to the AWSSQSConfig class. AWSSqsEndpoint inside the instance of the class is always NULL.
import com.amazonaws.services.sqs.AmazonSQSAsync;
import com.lonewolf.formsbuilder.config.AWSSQSConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.aws.core.env.ResourceIdResolver;
import org.springframework.cloud.aws.messaging.core.QueueMessagingTemplate;
import org.springframework.test.context.TestPropertySource;
import static org.junit.Assert.assertNotNull;
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
#TestPropertySource(properties = {
"cloud.aws.region.static=us-east-1",
"cloud.aws.credentials.accessKey=zzzzz",
"cloud.aws.credentials.secretKey=zzzzzz",
"aws.sqs.endpoint = http://localhost:9324",
"aws.sqs.requestQueue = CreateSchemaRequest",
"aws.sqs.responseQueue = CreateSchemaResponse"
})
public class AWSSQSConfigTests {
#Mock
private AmazonSQSAsync amazonSqs;
#Mock
private ResourceIdResolver resourceIdResolver;
#Test
public void contextLoads() {
AWSSQSConfig config = new AWSSQSConfig();
QueueMessagingTemplate queueMessagingTemplate = config.queueMessagingTemplate(amazonSqs, resourceIdResolver);
assertNotNull("The response body must not be null", queueMessagingTemplate);
}
}
Is this a chicken and the egg situation, where the spring framework actually needs to run first to inject those config values? Do we need an integration test here instead?
EDIT with working solution...
Using the accepted answer, here is my working test! I was able to remove my dependency of the Spring framework.
#RunWith(MockitoJUnitRunner.class)
public class AWSSQSConfigTests {
#Mock
private AmazonSQSAsync amazonSqs;
#Mock
private ResourceIdResolver resourceIdResolver;
#InjectMocks
private AWSSQSConfig config;
#Before
public void setup() {
ReflectionTestUtils.setField(config, "AWSSqsEndpoint", "http://fake");
}
#Test
public void contextLoads() {
QueueMessagingTemplate queueMessagingTemplate = config.queueMessagingTemplate(amazonSqs, resourceIdResolver);
assertNotNull("The response body must not be null", queueMessagingTemplate);
}
}
Have you tried injecting mock to your class (or autowire it), and then setting that field it using ReflectionTestUtils? This is a nice test utils class that Spring provides that allows you to do something like what you want without doing code modifications.
I mean something like this:
#InjectMocks
private AWSSQSConfig awssqsConfig;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(awssqsConfig, "AWSSqsEndpoint", "putYourEndpointHere");
}

Categories