I'm trying to test my LoginCacheServiceImpl but having problems. I followed this tutorial http://crunchify.com/how-to-create-a-simple-in-memory-cache-in-java-lightweight-cache/ and called it InMemoryCache with my own model object LoginEvent.
#Service
#PropertySource("classpath:app.properties")
public class LoginCacheServiceImpl {
#Value("$app{timeToLive}")
private long timeToLive;
#Value("$app{timerInterval}")
private long timerInterval;
private InMemoryCache<String, LoginEvent> cache;
#Override
public InMemoryCache<String, LoginEvent> getCache() {
return cache;
}
#PostConstruct
public void init() {
cache = new InMemoryCache<>(timeToLive, timerInterval, 10000);
}
}
The code runs fine but I'm trying to write unit tests for it. In the test class:
#TestPropertySource("classpath:app-test.properties")
#ContextConfiguration(classes = { LoginCacheServiceImplTest.class, LoginCacheServiceImpl.class })
#RunWith(SpringJUnit4ClassRunner.class)
public class LoginCacheServiceImplTest
#Autowired
private Environment environment;
private LoginCacheServiceImpl cacheService;
#Before
public void setUp() {
String test = environment.getProperty("timeToLive"); // this value gets parsed correctly
cacheService = new LoginCacheServiceImpl();
}
#Test
public void doNothing() {
System.out.println("test");
}
}
I get the error:
Caused by: org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'long'; nested exception is java.lang.NumberFormatException: For input string: "$app{timeToLive}"
at org.springframework.beans.TypeConverterSupport.doConvert(TypeConverterSupport.java:77)
at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:54)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:968)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:949)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:533)
... 42 more
Caused by: java.lang.NumberFormatException: For input string: "$app{timeToLive}"
I'm not sure how I should either rewrite my LoginCacheServiceImpl to make it testable, or preferably, to set up my test class so PostConstruct parses the timeToLive and timerInterval properly.
app-test.properties:
timeToLive=1000
timerInterval=1000
As an aside, I'm using Mockito 2.X. Can you use both MockitoJUnitRunner and SpringJUnit4ClassRunner? If not, how do you choose which one to use (sorry, new to Java).
Edit:
If I have this line
#ContextConfiguration(classes = { LoginCacheServiceImplTest.class, LoginCacheServiceImpl.class })
the unit test gives me that numbeformatexception error when I try to run the test as it is trying to create LoginCacheServiceImpl. But if I change it to
#ContextConfiguration(classes = { LoginCacheServiceImplTest.class })
and just do
String prop = environment.getProperty("timeToLive");
where in my app-test.properties file has
timeToLive=1000
the unit test can read that properties file. The timeToLive=1000 is a different number than I have in the original app.properties file so I do not think it's an issue of finding the app-test.properties file.
Please check the below updated class:
#Service
#PropertySource("classpath:app.properties")
public class LoginCacheServiceImpl {
#Value("${timeToLive}")
private long timeToLive;
#Value("${timerInterval}")
private long timerInterval;
private InMemoryCache<String, LoginEvent> cache;
#Override
public InMemoryCache<String, LoginEvent> getCache() {
return cache;
}
#PostConstruct
public void init() {
cache = new InMemoryCache<>(timeToLive, timerInterval, 10000);
}
}
if your propertySource able to find app.properties on classpath correctly, you can use ${keyname} to access the property.
Environment object already have properties configured by spring container as it extend PropertyResolver. check the docs for the same.Environment spring
or instead of using #Value, you can autowire the #Environment and use it in your service class.
Related
I am using simple converter for converting string to enum. Here is the custom converter:
#Component
public class SessionStateConverter implements Converter<String, UserSessionState> {
#Override
public UserSessionState convert(String source) {
try {
return UserSessionState.valueOf(source.toUpperCase());
} catch (Exception e) {
LOG.debug(String.format("Invalid UserSessionState value was provided: %s", source), e);
return null;
}
}
}
Currently I am using UserSessionState as PathVariable in my rest controller. The implementation works as expected. However when I try to unit test the rest controller it seems that conversion does not work and it does not hit the controller method.
#RunWith(MockitoJUnitRunner.class)
public class MyTest {
private MockMvc mockMvc;
#Mock
private FormattingConversionService conversionService;
#InjectMocks
private MynController controller;
#Before
public void setup() {
conversionService.addConverter(new SessionStateConverter());
mockMvc = MockMvcBuilders.standaloneSetup(controller).setConversionService(conversionService).build();
}
#Test
public void testSetLoginUserState() throws Exception {
mockMvc.perform(post("/api/user/login"));
}
}
In debug mode I get following error:
nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'rest.api.UserSessionState': no matching editors or conversion strategy found
In my opinion the mock for conversion service does not work.
Any ideas?
The conversionService is a mock.
So this:
conversionService.addConverter(new SessionStateConverter());
calls addConverter on mock. This does nothing useful for you.
I believe you want to use real FormattingConversionService: to do it you need to remove #Mock annotation from conversionService field and use a real instance of FormattingConversionService instead:
private FormattingConversionService conversionService = new FormattingConversionService();
If you need to track invocations on real objects as you would do on mock check out : #Spy
If anyone uses implements org.springframework.core.convert.converter.Converter<IN,OUT> and if you are getting similar error while using mockMvc, please follow the below method.
#Autowired
YourConverter yourConverter;
/** Basic initialisation before unit test fires. */
#Before
public void setUp() {
FormattingConversionService formattingConversionService=new FormattingConversionService();
formattingConversionService.addConverter(yourConverter); //Here
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(getController())
.setConversionService(formattingConversionService) // Add it to mockito
.build();
}
My Junit is not picking up properties set in test properties file.
I get no error, but value returned from properties file is null
CLASS TO BE TESTED:
package com.abc.mysource.mypackage;
#Component
#ComponentScan
public class MyHelper {
#Autowired
#Qualifier("commonProperties")
private CommonProperties commonProperties;
public LocalDateTime method1ThatUsesCommonProperties(LocalDateTime startDateTime) throws Exception {
String customUserType = commonProperties.getUserType(); // Returns null if run as JUnit test
//Further processing
}
}
SUPPORTING COMPONENTS - BEANS & CONFIG CLASSES:
package com.abc.mysource.mypackage;
#Component
public class CommonProperties {
#Value("${myhelper.userType}")
private String userType;
public String getUserType() {
return userType;
}
public void setCalendarType(String userType) {
this.userType = userType;
}
}
CONFIG CLASS:
package com.abc.mysource.mypackage;
#Configuration
#ComponentScan(basePackages ="com.abc.mysource.mypackage.*")
#PropertySource("classpath:default.properties")
public class CommonConfig {}
default.properties under src/main/resources
myhelper.userType=PRIORITY
MY TEST CLASS:
package com.abc.mysource.mypackage.test;
#RunWith(SpringRunner.class)
#ContextConfiguration(classes=MyHelper.class)
#TestPropertySource("classpath:default-test.properties")
#EnableConfigurationProperties
public class MyHelperTest {
#MockBean(name="commonProperties")
private CommonProperties commonProperties;
#Autowired
private MyHelper myHelper;
#Test
public void testMethod1ThatUsesCommonProperties() {
myHelper.method1ThatUsesCommonProperties();
}
}
default-test.properties defined under /src/test/resources:
myhelper.userType=COMMON
NOTE:
I moved default-test.properties to /src/main/resources - commonProperties.getUserType() is null
I even used #TestPropertySource(properties = {"myhelper.userType=COMMON"}). Same result
NOTE 2:
I tried the solution on #TestPropertySource is not loading properties.
This solution requires me to create a duplicate bean called CommonProperties under src/test/java. But #MockBean fails when I do
#MockBean(name="commonProperties")
private CommonProperties commonProperties;
Please do not mark a duplicate.
NOTE 3:
Mine is a spring, not a spring boot application.
MockBeans are suited if you don't need specific state. Usually this bean is "isolated" and every method call of this bean will have the same result. It is "isolated" -> the service that uses #Value annotation will not apply on this bean.
What you need is a "normal" bean, properly constructed and initialized. Please use #Autowired annotation and define another bean if needed, using a test profile.
Basically, the question is in the title.
I faced a problem that in post-construct phase my bean (that is autowired in the bean that is going through post-construct phase right now) is already mocked, but all the behavior described by Mockito.when() doesn't work, all the calls return null.
While searching I found this solution.
But is it possible to make it work without using any 3rd party libraries?
Test class:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#ContextConfiguration(classes = TestApplicationConfiguration.class)
public class ServiceTest {
#Autowired
#Qualifier("test")
private PCLPortType pclPortType;
#MockBean
private ClearingHelper сlearingHelper;
#MockBean
private OrganizationCacheRepository organizationCacheRepository;
#Before
public void setup() throws Exception{
OperationResultWithOrganizationSystemIdMappingList res = new OperationResultWithOrganizationSystemIdMappingList();
when(clearingHelper.getOrgIdSystemIdMapping(any(Keycloak.class))).thenReturn(res);
}
#Test
public void test() throws Exception{
pclPortType.call("123");
}
}
Test config:
#TestConfiguration
public class TestApplicationConfiguration {
#Bean(name = "test")
public PCLPortType pclPortTypeForTest() throws JAXBException {
...
}
#Bean
public Keycloak keycloak() {
return Mockito.mock(Keycloak.class);
}
}
Component where I want to get mocked beans:
#Component
public class OrganizationCacheJob {
private static final Logger logger =
LogManager.getLogger(OrganizationCacheJob.class);
private final ObjectFactory<Keycloak> factory;
private final ClearingHelper clearingHelper;
private final OrganizationCacheRepository organizationCacheRepository;
#Autowired
public OrganizationCacheJob(ObjectFactory<Keycloak> factory,
ClearingHelper clearingHelper,
OrganizationCacheRepository organizationCacheRepository) {
this.factory = factory;
this.clearingHelper = ClearingHelper;
this.organizationCacheRepository = organizationCacheRepository;
}
#PostConstruct
public void updateCacheRepository() {
doUpdateCacheRepository();
}
#Scheduled(cron = "${organization.cache.schedule}")
public void start() {
logger.info("Starting update organization cache.");
doUpdateCacheRepository();
logger.info("Job finished.");
}
private void doUpdateCacheRepository() {
try {
Keycloak keycloak = factory.getObject();
OperationResultWithOrganizationSystemIdMappingList orgIdSystemIdMapping = clearingHelper.getOrgIdSystemIdMapping(keycloak);
if (orgIdSystemIdMapping != null) {
orgIdSystemIdMapping.getContent().forEach(o -> organizationCacheRepository.saveOrgIdsSystemsIdsMappings(o.getOrgId(), o.getId()));
logger.debug("Was saved {} orgIds", orgIdSystemIdMapping.getContent().size());
}
} catch (Exception e) {
logger.error("Error fetching whole mapping for org and systems ids. Exception: {}", e);
}
}
}
So, in post-construct phase of OrganizationCacheJob I want to get res when calling clearingHelper, but instead I get null.
ClearingHelper is a regular Spring bean marked as a #Component with public methods.
Ahh ok I just realized - when you start your test case, whole env is up and running first, then you advance to testing phase. So, translating to your case - first you got injection and post-constructs called, then #Before method is done, thus the result.
So as you can see, code says more than all the words you could put in your original post.
If it is possible for you, use spies insteed of mocks. If it is not possible to construct that, you will have to redesign your tests to not rely on post construct.
In this case, since you want the same post-construct behavior for every test case, provide own factory method for given mock (like you did with keycloak) and move when-doReturn there. It will be guaranteed that it will happen before post construct.
I have a test class which loads a test spring application context, now I want to create a junit rule which will setup some test data in mongo db. For this I created a rule class.
public class MongoRule<T> extends ExternalResource {
private MongoOperations mongoOperations;
private final String collectionName;
private final String file;
public MongoRule(MongoOperations mongoOperations, String file, String collectionName) {
this.mongoOperations = mongoOperations;
this.file = file;
this.collectionName = collectionName;
}
#Override
protected void before() throws Throwable {
String entitiesStr = FileUtils.getFileAsString(file);
List<T> entities = new ObjectMapper().readValue(entitiesStr, new TypeReference<List<T>>() {
});
entities.forEach((t) -> {
mongoOperations.save(t, collectionName);
});
}
}
Now I am using this rule inside my test class and passing the mongoOperations bean.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = SpringTestConfiguration.class)
public class TransactionResourceTest {
#Autowired
private ITransactionResource transactionResource;
#Autowired
private MongoOperations mongoOperations;
#Rule
public MongoRule<PaymentInstrument> paymentInstrumentMongoRule
= new MongoRule(mongoOperations, "paymentInstrument.js", "paymentInstrument");
....
}
The problem is that Rule is getting executed before application context gets loaded, so mongoOperations reference is passed as null. Is there a way to make rules run after the context is loaded?
As far as I know what you are trying to achieve is not possible in such straight forward way because:
the rule is instantiated prior Spring's Application Context.
SpringJUnit4ClassRunner will not attempt to inject anything on the rule's instance.
There is an alternative described here: https://blog.jayway.com/2014/12/07/junit-rule-spring-caches/ but I think it would fall short in terms of what can be loaded into mongodb.
In order to achieve what you want to achieve, you would probably require a test execution listener that would inject whatever dependencies you require on your rule object.
Here's a solution, using some abstract super class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = SpringTestConfiguration.class)
public abstract class AbstractTransactionResourceTest<T> {
#Autowired
private ITransactionResource transactionResource;
#Autowired
private MongoOperations mongoOperations;
#Before
public void setUpDb() {
String entitiesStr = FileUtils.getFileAsString(entityName() + ".js");
List<T> entities = new ObjectMapper().readValue(entitiesStr, new TypeReference<List<T>>() {});
entities.forEach((t) -> {
mongoOperations.save(t, entityName());
});
}
protected abstract String entityName();
}
then
public class TransactionResourceTest extends AbstractTransactionResourceTest<PaymentInstrument> {
#Override
protected String entityName() {
return "paymentInstrument";
};
// ...
}
I have the following spring test configuration with different profiles:
#Configuration
#ComponentScan (value = {"uk.co.test.ws" })
public class SpringTestConfig
{
#Profile( "local")
#PropertySource( "classpath:/config/local/settings.properties" )
public static class SpringTestConfigLocal
{
#Autowired
private Environment environment ;
#Bean(name= "baseUrl" )
public String setBaseUrl()
{
return environment .getRequiredProperty("baseurl.protocol" )+"://" +environment .getRequiredProperty( "baseurl.host");
}
}
and then created a base class that takes in the base url
> #RunWith (SpringJUnit4ClassRunner. class) #ContextConfiguration
> (classes = { SpringTestConfig. class }) public class BaseWsTest {
> #Autowired
> String baseUrl;
which then gets extended to other test classes like below:
public class SampleFTest extends BaseWsTest
{
#Test
public void hello() throws FileNotFoundException, Exception
{
System. out .println("base url: " + baseUrl );
When run using normal maven clean install the tests works but if I was to run it by right-clicking the method it gets a
Error creating bean with name 'uk.co.test.ws.service.base.SampleFTest': Injection of autowired dependencies failed;
You forgot to choose the profile in your SampleFTest class, you should add this line:
#ActiveProfiles(profiles = "local")
In this way the SpringTestConfigLocal will be initialized and the baseUrl bean available
EDIT: I would add a property in a .properties file so I could use a variable myprofile:
#ActiveProfiles(profiles = "${myprofile}")
And eventually, if you don't want to change the value from time to time, I would apply some logic in order to load one property file or another.
EDIT 2: I'm sorry but this doesn't work because the file is loaded after the assigment of the annotation EL value, but you can add this:
spring.profiles.active=local
to the property file and this will do the same as putting the annotation #IntegrationTest("local"). This is the code I tried:
#TestPropertySource(value="classpath:/config/test.properties")//, properties={"myaddress=cane", "myprofile=local"})
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
#IntegrationTest("${myaddress}")
//#ContextConfiguration
//#ActiveProfiles(profiles = "${myprofile}")
public class BasicJUnitTest{
protected Logger logger;
protected MockMvc mockMvc;
#Autowired
private Environment env;
#Value("${myaddress}" )
String myval;
public BasicJUnitTest(){
this.logger = LoggerFactory.getLogger(this.getClass());
}
#Test
public void test(){
logger.info("hola"+myval+ " " + env.getActiveProfiles()[0]);
}
}