How to test #Cacheable? - java

I am struggling with testing #Cacheable within a Spring Boot Integration Test. This is my second day learning how to do Integration Tests and all of the examples I have found use older versions. I also saw an example of assetEquals("some value", is()) but nothing with an import statement to know which dependency "is" belongs to. The test fails at the second
This is my integration test....
#RunWith(SpringRunner.class)
#DataJpaTest // used for other methods
#SpringBootTest(classes = TestApplication.class)
#SqlGroup({
#Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD,
scripts = "classpath:data/Setting.sql") })
public class SettingRepositoryIT {
#Mock
private SettingRepository settingRepository;
#Autowired
private Cache applicationCache;
#Test
public void testCachedMethodInvocation() {
List<Setting> firstList = new ArrayList<>();
Setting settingOne = new Setting();
settingOne.setKey("first");
settingOne.setValue("method invocation");
firstList.add(settingOne);
List<Setting> secondList = new ArrayList<>();
Setting settingTwo = new Setting();
settingTwo.setKey("second");
settingTwo.setValue("method invocation");
secondList.add(settingTwo);
// Set up the mock to return *different* objects for the first and second call
Mockito.when(settingRepository.findAllFeaturedFragrances()).thenReturn(firstList, secondList);
// First invocation returns object returned by the method
List<Setting> result = settingRepository.findAllFeaturedFragrances();
assertEquals("first", result.get(0).getKey());
// Second invocation should return cached value, *not* second (as set up above)
List<Setting> resultTwo = settingRepository.findAllFeaturedFragrances();
assertEquals("first", resultTwo.get(0).getKey()); // test fails here as the actual is "second."
// Verify repository method was invoked once
Mockito.verify(settingRepository, Mockito.times(1)).findAllFeaturedFragrances();
assertNotNull(applicationCache.get("findAllFeaturedFragrances"));
// Third invocation with different key is triggers the second invocation of the repo method
List<Setting> resultThree = settingRepository.findAllFeaturedFragrances();
assertEquals(resultThree.get(0).getKey(), "second");
}
}
ApplicationContext, components, entities, repositories and service layer for tests. The reason why I do it this way is because this maven module is used in other modules as a dependency.
#ComponentScan({ "com.persistence_common.config", "com.persistence_common.services" })
#EntityScan(basePackages = { "com.persistence_common.entities" })
#EnableJpaRepositories(basePackages = { "com.persistence_common.repositories" })
#SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Cache config....
#Configuration
#EnableCaching
public class CacheConfig {
public static final String APPLICATION_CACHE = "applicationCache";
#Bean
public FilterRegistrationBean registerOpenSessionInViewFilterBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
OpenEntityManagerInViewFilter filter = new OpenEntityManagerInViewFilter();
registrationBean.setFilter(filter);
registrationBean.setOrder(5);
return registrationBean;
}
#Bean
public Cache applicationCache() {
return new GuavaCache(APPLICATION_CACHE, CacheBuilder.newBuilder()
.expireAfterWrite(30, TimeUnit.DAYS)
.build());
}
}
The repository under test....
public interface SettingRepository extends JpaRepository<Setting, Integer> {
#Query(nativeQuery = true, value = "SELECT * FROM Setting WHERE name = 'featured_fragrance'")
#Cacheable(value = CacheConfig.APPLICATION_CACHE, key = "#root.methodName")
List<Setting> findAllFeaturedFragrances();
}

The first problem with SettingRepositoryIT is, the #Mock anotation on the field settingRepository. This is paradox for any normal-test, integration-test or any else.
You should let Spring bring in the dependencies for the class-under-test, which is SettingRepository in your case.
Please look at this example how #Autowired is used for the class-under-test, which is OrderService in this example:
#RunWith(SpringRunner.class)
// ApplicationContext will be loaded from the
// static nested Config class
#ContextConfiguration
public class OrderServiceTest {
#Configuration
static class Config {
// this bean will be injected into the OrderServiceTest class
#Bean
public OrderService orderService() {
OrderService orderService = new OrderServiceImpl();
// set properties, etc.
return orderService;
}
}
#Autowired
private OrderService orderService;
#Test
public void testOrderService() {
// test the orderService
}
}
Go for the documentation with the full example: ยง 15. Integration Testing
The second problem is that you do not have to test #Cachable. You should only test your implementation. Here is a very good example from Oliver Gierke on how you should test it: How to test Spring's declarative caching support on Spring Data repositories?

In my case I wanted to validate the expression in the unless expression in the #Cacheable annotation, so I think it makes perfect sense and I'm not testing Spring's code.
I managed to test it without using Spring Boot, so it is plain Spring test:
#RunWith(SpringRunner.class)
#ContextConfiguration
public class MyTest {
private static MyCacheableInterface myCacheableInterfaceMock = mock(MyCacheableInterface.class);
#Configuration
#EnableCaching
static class Config {
#Bean
public MyCacheableInterface myCacheableInterface() {
return myCacheableInterfaceMock;
}
#Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("myObject");
}
}
#Autowired
private MyCacheableInterface myCacheableInterface;
#Test
public void test() {
when(myCacheableInterfaceMock.businessMethod(anyString())).then(i -> {
List<MyObject> list = new ArrayList<>();
list.add(new MyObject(new Result("Y")));
return list;
});
myCacheableInterface.businessMethod("test");
verify(myCacheableInterfaceMock).businessMethod(anyString());
myCacheableInterface.businessMethod("test");
verifyNoMoreInteractions(myCacheableInterfaceMock);
}
}
In MyCacheableInterface I have the following annotation:
public interface MyCacheableInterface {
#Cacheable(value = "myObject", unless = "#result.?[Result.getSuccess() != 'Y'].size() == #result.size()")
List<MyObject> businessMethod(String authorization);
}

Related

How to test multiple instance creation of a bean of one class filled with values from application.properties?

After creating multiple beans of a specific class using #PostConstruct according to Multiple instances of a bean of one class filled with values from application.properties
I wonder how I could test the creation of those bean instances.
I tried it this way:
#ActiveProfiles("test")
#RequiredArgsConstructor
#ExtendWith({SpringExtension.class, MockitoExtension.class})
#SpringBootTest(classes = {AppHealthCheckContributor.class, AppHealthCheckProperties.class})
#ConfigurationProperties(prefix = "healthcheck")
#ContextConfiguration(classes = { AppHealthCheckConfig.class })
//#TestPropertySource(properties = "healthcheck.app[0].name=testHealthIndicator, healthcheck.app[0].baseUrl=http://localhost, healthcheck.app[0].basePath=/test")
#TestPropertySource(locations = {"/application-test.properties"})
class AppHealthCheckConfigTest {
#Autowired
private AdapterHealthCheckConfig config;
#Test
void healthCheckBeansAreInitialized() {
// config.init();
System.out.println(config.getApps().get(0));
}
}
but this results in: AppHealthCheckProperties(baseUrl=null, basePath=null, name=null)
Try the following:
#ExtendWith(SpringExtension.class)
#EnableConfigurationProperties(value = AdapterHealthCheckConfig.class)
#TestPropertySource("classpath:application-test.properties")
class AppHealthCheckConfigTest {
#Autowired
private AdapterHealthCheckConfig config;
#Test
void healthCheckBeansAreInitialized() {
// config.init();
System.out.println(config.getApps().get(0));
}
}

Mocked class is showing as null on unit test execution

I wrote a unit test as shown below, following an example of another unit test I have seen, but for some reason when I run the test, inside the class I am testing the serviceProperties object is shown as null, I thought this approach would of given me a mocked value.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class)
#Category({ UnitTests.class })
public class MyTest {
#Mock
private OAuth2RestTemplate serviceRestTemplate;
#Mock
// had to make this static to use inside TestConfiguration
static
serviceProperties serviceProperties;
#Configuration
#EnableConfigurationProperties
#Import({ PropertiesTestConfiguration.class, RestTestConfiguration.class })
static class TestConfiguration {
#Bean
MyInterface MyInterface() {
return new MyInterface(serviceProperties);
}
}
#Autowired
MyInterface MyInterface;
#Test
public void getServiceResponse_Success() throws ServiceException {
Mockito.when(serviceProperties.getUrl()).thenReturn("http://test_url");
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(serviceProperties.getUrl() + VALID_NUMBER + HEADER_ENDPOINT);
ResponseEntity<String> mockResponseEntity = new ResponseEntity<String>(mockResponseBody, HttpStatus.OK);
thrown.none();
MyInterface.getClaimByClaimId(VALID_CLAIM_NUMBER);
}
}
The #Mock annotation has no effect without annotating the class with #ExtendWith(MockitoExtension.class).

Spring Boot 2 - Testing #Cacheable with Mockito for method without arguments is not working

I have an application using Spring Boot 2. I would like to test a method with #Cacheable (Spring Cache) on it. I made a simple example in order to show the idea:
#Service
public class KeyService {
#Cacheable("keyCache")
public String getKey() {
return "fakeKey";
}
}
And the test class:
#RunWith(SpringRunner.class)
#SpringBootTest
public class KeyServiceTest {
#Autowired
private KeyService keyService;
#Test
public void shouldReturnTheSameKey() {
Mockito.when(keyService.getKey()).thenReturn("key1", "key2");
String firstCall = keyService.getKey();
assertEquals("key1", firstCall);
String secondCall = keyService.getKey();
assertEquals("key1", secondCall);
}
#EnableCaching
#Configuration
static class KeyServiceConfig {
#Bean
KeyService keyService() {
return Mockito.mock(KeyService.class);
}
#Bean
CacheManager cacheManager() {
return new ConcurrentMapCacheManager("keyCache");
}
}
}
The example above does not work. But, if I change the getKey() method to receive a parameter:
#Service
public class KeyService {
#Cacheable("keyCache")
public String getKey(String param) {
return "fakeKey";
}
}
And refactor the test to accommodate that change, the test works successfully:
#RunWith(SpringRunner.class)
#SpringBootTest
public class KeyServiceTest {
#Autowired
private KeyService keyService;
#Test
public void shouldReturnTheSameKey() {
Mockito.when(keyService.getKey(Mockito.anyString())).thenReturn("key1", "key2");
String firstCall = keyService.getKey("xyz");
assertEquals("key1", firstCall);
String secondCall = keyService.getKey("xyz");
assertEquals("key1", secondCall);
}
#EnableCaching
#Configuration
static class KeyServiceConfig { //The same code as shown above }
}
Do you guys have any idea about this issue?
a cache lookup is performed using as key the method parameters. It means you need a key for methods that don't have params. Try this #Cacheable(value = "keyCache", key = "#root.methodName")
I wonder if you're running into an issue with the default key generation strategy: spring documentation. That seems to be the big difference between the two. It's changing what it's using for the key although I would think either should work.

Spring + TestNG Autowiring failure - NOT due to "new"

Having an issue with Spring Autowiring. I have an Integration Test class declared as follows:
#ContextConfiguration(classes = TestConfig.class, loader = AnnotationConfigContextLoader.class)
public abstract class BaseIntegrationTest
extends AbstractTestNGSpringContextTests {
#Autowired
protected TestProperties properties;
//... more stuff here
}
The Context configuration looks like this:
#Configuration
#EnableAspectJAutoProxy
#ComponentScan(basePackages = {"com.ourcompany.myapp.it"},
excludeFilters = #ComponentScan.Filter(value = com.inin.wfm.it.config.ConfigPackageExcludeFilter.class, type = FilterType.CUSTOM))
public class TestConfig {
private TestProperties testProperties;
private PropertyService propertyService;
//This both creates and registers the bean with Spring
#Bean
public TestProperties getTestProperties() throws IOException {
if (testProperties == null) {
testProperties = new TestProperties(propertyService());
}
return testProperties;
}
#Bean
public PropertyService propertyService() throws IOException {
if (propertyService == null) {
AppAdminConfig config = new AppAdminConfig.Builder(PropertyService.getEnvironment(), TestConfigKey.ApplicationId)
.checkPropertyHasValue(GlobalConfigKey.KafkaBrokerList.key())
.checkPropertyHasValue(GlobalConfigKey.ZookeeperList.key())
.build();
propertyService = new PropertyService(config.getPropertiesConfig());
propertyService.initialize();
}
return propertyService;
}
}
And this is the bean I'm having trouble with:
#Configurable
public class TestProperties {
private PropertyService propertyService;
public TestProperties(PropertyService propertyService) {
this.propertyService = propertyService;
}
public String getCacheUri(){
return propertyService.getPropertyRegistry().getString(TestConfigKey.CacheUri.key(), Default.CACHE_URI);
}
}
I have multiple Test implementation classes that extend BaseIntegrationTest. All of them but one have valid references to their TestProperties field, but exactly one of the test implementation classes is getting a Null Pointer and throwing an NPE when an attempt is made to reference this.
So the question is, why is Spring #Autowire working fine for 3 different classes that extend the same base class, but wiring up null for the fourth? There is no additional configuration logic in any of the implementations.
For MCVE completeness, here's the bare bones of my impl class
public class CacheIT
extends BaseIntegrationTest {
#Test
public void testUserCache() throws InterruptedException, ExecutionException, TimeoutException {
String uri = properties.getCacheUri() //TODO - NPE here
}
}
I know there's not much to go on... I've been working with Spring for a long time and haven't seen it do this kind of thing before. According to everything I can see it should be working.

Mocking config with spring boot isn't picking up property files

I am trying to write a IntegrationFlow test. It goes something like this:
JMS(in) -> (find previous versions in db) -> reduce(in,1...n) -> (to db) -> JMS(out)
So, no suprise: I want to mock the DB calls; they are Dao beans. But, I also want it to pickup other beans through component scan; I will selectively scan all packages except dao.
Create a test config and mock the Daos. No problem
Follow spring boot instructions for testing to get Component scanned beans. No problem
I just want to verify the sequence of steps and the resultant output as the outbound JMS queue would see it. Can someone just help me fill in the blanks?
This CANT be tough! The use of mocks seems to be problematic because plenty of essential fields are final. I am reading everywhere about this and just not coming up with a clear path. I inherited this code BTW
My error:
org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers
Here is my code
#Configuration
#ImportResource("classpath:retry-context.xml")
public class LifecycleConfig {
#Autowired
private MessageProducerSupport inbound;
#Autowired
private MessageHandler outbound;
#Autowired
#Qualifier("reducer")
private GenericTransformer<Collection<ExtendedClaim>,ExtendedClaim> reducer;
#Autowired
#Qualifier("claimIdToPojo")
private GenericTransformer<String,ClaimDomain> toPojo;
#Autowired
#Qualifier("findPreviousVersion")
private GenericTransformer<ExtendedClaim,Collection<ExtendedClaim>> previousVersions;
#Autowired
#Qualifier("saveToDb")
private GenericHandler<ExtendedClaim> toDb;
#Bean
public DirectChannel getChannel() {
return new DirectChannel();
}
#Bean
#Autowired
public StandardIntegrationFlow processClaim() {
return IntegrationFlows.from(inbound).
channel(getChannel()).
transform(previousVersions).
transform(reducer).
handle(ExtendedClaim.class,toDb).
transform(toPojo).
handle(outbound).get();
}
}
Test Config
#Configuration
public class TestConfig extends AbstractClsTest {
#Bean(name = "claimIdToPojo")
public ClaimIdToPojo getClaimIdToPojo() {
return spy(new ClaimIdToPojo());
}
#Bean
public ClaimToId getClaimToIdPojo() {
return spy(new ClaimToId());
}
#Bean(name = "findPreviousVersion")
public FindPreviousVersion getFindPreviousVersion() {
return spy(new FindPreviousVersion());
}
#Bean(name = "reducer")
public Reducer getReducer() {
return spy(new Reducer());
}
#Bean(name = "saveToDb")
public SaveToDb getSaveToDb() {
return spy(new SaveToDb());
}
#Bean
public MessageProducerSupport getInbound() {
MessageProducerSupport mock = mock(MessageProducerSupport.class);
// when(mock.isRunning()).thenReturn(true);
return mock;
}
#Bean
public PaymentDAO getPaymentDao() {
return mock(PaymentDAO.class);
}
#Bean
public ClaimDAO getClaimDao() {
return mock(ClaimDAO.class);
}
#Bean
public MessageHandler getOutbound() {
return new CaptureHandler<ExtendedClaim>();
}
}
Actual test won't load
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {TestConfig.class, LifecycleConfig.class})
public class ClaimLifecycleApplicationTest extends AbstractClsTest {
#Autowired
private MessageHandler outbound;
#Autowired
#Qualifier("reducer")
private GenericTransformer<Collection<ExtendedClaim>,ExtendedClaim> reducer;
#Autowired
#Qualifier("claimIdToPojo")
private GenericTransformer<String,ClaimDomain> toPojo;
#Autowired
#Qualifier("findPreviousVersion")
private GenericTransformer<ExtendedClaim,Collection<ExtendedClaim>> previousVersions;
#Autowired
#Qualifier("saveToDb")
private GenericHandler<ExtendedClaim> toDb;
#Autowired
private DirectChannel defaultChannel;
#Test
public void testFlow() throws Exception {
ExtendedClaim claim = getClaim();
Message<ExtendedClaim> message = MessageBuilder.withPayload(claim).build();
List<ExtendedClaim> previousClaims = Arrays.asList(claim);
defaultChannel.send(message);
verify(previousVersions).transform(claim);
verify(reducer).transform(previousClaims);
verify(toDb).handle(claim, anyMap());
verify(toPojo).transform(claim.getSubmitterClaimId());
verify(outbound);
}
}
There are a lot of domain-specific object, so I can't test it to reproduce or find some other issue with your code.
But I see that you don't use an #EnableIntegration on your #Configurations classes.

Categories