I am completely stumped. I am new to Spring Batch testing and I have found countless examples that have left me confused.
I'm trying to test a Spring Batch decider. This decider checks to see if certain JSON files exist before continuing.
To begin, I have a BatchConfiguration file marked with #Configuration in my Spring Batch project.
In the BatchConfiguration, I have a ImportJsonSettings bean which loads its properties from settings in the application.properties file.
#ConfigurationProperties(prefix="jsonfile")
#Bean
public ImportJSONSettings importJSONSettings(){
return new ImportJSONSettings();
}
When running the Spring Batch application, this works perfectly.
Next, here are the basics of the JsonFilesExistDecider , which Autowires a FileRetriever object...
public class JsonFilesExistDecider implements JobExecutionDecider {
#Autowired
FileRetriever fileRetriever;
#Override
public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) { ... }
The FileRetriever object itself Autowires the ImportJSONSettings object.
Here is the FileRetriever...
#Component("fileRetriever")
public class FileRetriever {
#Autowired
private ImportJSONSettings importJSONSettings;
private File fieldsFile = null;
public File getFieldsJsonFile(){
if(this.fieldsFile == null) {
this.fieldsFile = new File(this.importJSONSettings.getFieldsFile());
}
return this.fieldsFile;
}
}
Now for the test file. I am using Mockito for testing.
public class JsonFilesExistDeciderTest {
#Mock
FileRetriever fileRetriever;
#InjectMocks
JsonFilesExistDecider jsonFilesExistDecider;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testDecide() throws Exception {
when(fileRetriever.getFieldsJsonFile()).thenReturn(new File(getClass().getResource("/com/files/json/fields.json").getFile()));
// call decide()... then Assert...
}
}
PROBLEM... The ImportJSONSettings object that is #Autowired in the FileRetriever object is always NULL.
When calling the testDecide() method, I get a NPE since calling the getFieldsJsonFile() in FileRetriever, the ImportJSONSettings bean does not exist.
How does the ImportJSONSettings bean get properly created in the FileRetriever object so it can be used??
I have tried adding the following to my test class, but it does not help.
#Mock
ImportJSONSettings importJSONSettings;
Do I need to create it independently? How does it get injected into the FileRetriever?
Any help would be appreciated.
Try changing the #Before annotation on the setup() method to #BeforeEach like so:
#BeforeEach
void setup() {
MockitoAnnotations.initMocks(this);
}
This could also be a dependency issue. Make sure you have a recent version of io.micrometer:micrometer-core. Would you be able to share your test dependencies?
If you have the above setup correctly, you shouldn't have to worry about whether or not ImportJSONSettings is null or not as long as you have getFieldsJsonFile() stubbed correctly.
Related
My Spring Boot project uses JUnit 5. I'd like to setup an integration test which requires a local SMTP server to be started, so I implemented a custom extension:
public class SmtpServerExtension implements BeforeAllCallback, AfterAllCallback {
private GreenMail smtpServer;
private final int port;
public SmtpServerExtension(int port) {
this.port = port;
}
#Override
public void beforeAll(ExtensionContext extensionContext) {
smtpServer = new GreenMail(new ServerSetup(port, null, "smtp")).withConfiguration(GreenMailConfiguration.aConfig().withDisabledAuthentication());
smtpServer.start();
}
#Override
public void afterAll(ExtensionContext extensionContext) {
smtpServer.stop();
}
}
Because I need to configure the server's port I register the extension in the test class like this:
#SpringBootTest
#AutoConfigureMockMvc
#ExtendWith(SpringExtension.class)
#ActiveProfiles("test")
public class EmailControllerIT {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#Value("${spring.mail.port}")
private int smtpPort;
#RegisterExtension
// How can I use the smtpPort annotated with #Value?
static SmtpServerExtension smtpServerExtension = new SmtpServerExtension(2525);
private static final String RESOURCE_PATH = "/mail";
#Test
public void whenValidInput_thenReturns200() throws Exception {
mockMvc.perform(post(RESOURCE_PATH)
.contentType(APPLICATION_JSON)
.content("some content")
).andExpect(status().isOk());
}
}
While this is basically working: How can I use the smtpPort annotated with #Value (which is read from the test profile)?
Update 1
Following your proposal I created a custom TestExecutionListener.
public class CustomTestExecutionListener implements TestExecutionListener {
#Value("${spring.mail.port}")
private int smtpPort;
private GreenMail smtpServer;
#Override
public void beforeTestClass(TestContext testContext) {
smtpServer = new GreenMail(new ServerSetup(smtpPort, null, "smtp")).withConfiguration(GreenMailConfiguration.aConfig().withDisabledAuthentication());
smtpServer.start();
};
#Override
public void afterTestClass(TestContext testContext) {
smtpServer.stop();
}
}
The listener is registered like this:
#TestExecutionListeners(value = CustomTestExecutionListener.class, mergeMode = MERGE_WITH_DEFAULTS)
When running the test the listener gets called but smtpPort is always 0, so it seems as if the #Value annotation is not picked up.
I don't think you should work with Extensions here, or in general, any "raw-level" JUnit stuff (like lifecycle methods), because you won't be able to access the application context from them, won't be able to execute any custom logic on beans and so forth.
Instead, take a look at Spring's test execution listeners abstraction
With this approach, GreenMail will become a bean managed by spring (probably in a special configuration that will be loaded only in tests) but since it becomes a bean it will be able to load the property values and use #Value annotation.
In the test execution listener you'll start the server before the test and stop after the test (or the whole test class if you need that - it has "hooks" for that).
One side note, make sure you mergeMode = MergeMode.MERGE_WITH_DEFAULTS as a parameter to #TestExecutionListeners annotation, otherwise some default behaviour (like autowiring in tests, dirty context if you have it, etc) won't work.
Update 1
Following Update 1 in the question. This won't work because the listener itself is not a spring bean, hence you can't autowire or use #Value annotation in the listener itself.
You can try to follow this SO thread that might be helpful, however originally I meant something different:
Make a GreenMail a bean by itself:
#Configuration
// since you're using #SpringBootTest annotation - it will load properties from src/test/reources/application.properties so you can put spring.mail.port=1234 there
public class MyTestMailConfig {
#Bean
public GreenMail greenMail(#Value(${"spring.mail.port"} int port) {
return new GreenMail(port, ...);
}
}
Now this configuration can be placed in src/test/java/<sub-package-of-main-app>/ so that in production it won't be loaded at all
Now the test execution listener could be used only for running starting / stopping the GreenMail server (as I understood you want to start it before the test and stop after the test, otherwise you don't need these listeners at all :) )
public class CustomTestExecutionListener implements TestExecutionListener {
#Override
public void beforeTestClass(TestContext testContext) {
GreenMail mailServer =
testContext.getApplicationContext().getBean(GreenMail.class);
mailServer.start();
}
#Override
public void afterTestClass(TestContext testContext) {
GreenMail mailServer =
testContext.getApplicationContext().getBean(GreenMail.class);
mailServer.stop();
}
}
Another option is autowiring the GreenMail bean and using #BeforeEach and #AfterEach methods of JUnit, but in this case you'll have to duplicate this logic in different Test classes that require this behavour. Listeners allow reusing the code.
I am trying to autowire a component into a custom JsonDeserializer but cannot get it right even with the following suggestions I found:
Autowiring in JsonDeserializer: SpringBeanAutowiringSupport vs HandlerInstantiator
Right way to write JSON deserializer in Spring or extend it
How to customise the Jackson JSON mapper implicitly used by Spring Boot?
Spring Boot Autowiring of JsonDeserializer in Integration test
My final goal is to accept URLs to resources in different microservices and store only the ID of the resource locally. But I don't want to just extract the ID from the URL but also verify that the rest of the URL is correct.
I have tried many things and lost track a bit of what I tried but I believe I tried everything mentioned in the links above. I created tons of beans for SpringHandlerInstantiator, Jackson2ObjectMapperBuilder, MappingJackson2HttpMessageConverter, RestTemplate and others and also tried with setting the SpringHandlerInstantiator in RepositoryRestConfigurer#configureJacksonObjectMapper.
I am using Spring Boot 2.1.6.RELEASE which makes me think something might have changed since some of the linked threads are quite old.
Here's my last attempt:
#Configuration
public class JacksonConfig {
#Bean
public HandlerInstantiator handlerInstantiator(ApplicationContext applicationContext) {
return new SpringHandlerInstantiator(applicationContext.getAutowireCapableBeanFactory());
}
}
#Configuration
public class RestConfiguration implements RepositoryRestConfigurer {
#Autowired
private Validator validator;
#Autowired
private HandlerInstantiator handlerInstantiator;
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
validatingListener.addValidator("beforeCreate", validator);
validatingListener.addValidator("beforeSave", validator);
}
#Override
public void configureJacksonObjectMapper(ObjectMapper objectMapper) {
objectMapper.setHandlerInstantiator(handlerInstantiator);
}
}
#Component
public class RestResourceURLSerializer extends JsonDeserializer<Long> {
#Autowired
private MyConfig config;
#Override
public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ServiceConfig serviceConfig = config.getServices().get("identity");
URI serviceUri = serviceConfig.getExternalUrl();
String servicePath = serviceUri.getPath();
URL givenUrl = p.readValueAs(URL.class);
String givenPath = givenUrl.getPath();
if (servicePath.equals(givenPath)) {
return Long.parseLong(givenPath.substring(givenPath.lastIndexOf('/') + 1));
}
return null;
}
}
I keep getting a NullPointerException POSTing something to the API endpoint that is deserialized with the JsonDeserializer above.
I was able to solve a similar problem by marking my deserializer constructor accept a parameter (and therefore removing the empty constructor) and marking constructor as #Autowired.
public class MyDeserializer extends JsonDeserializer<MyEntity> {
private final MyBean bean;
// no default constructor
#Autowired
public MyDeserializer(MyBean bean){
this.bean = bean
}
...
}
#JsonDeserialize(using = MyDeserializer.class)
public class MyEntity{...}
My entity is marked with annotation #JsonDeserialize so I don't have to explicitly register it with ObjectMapper.
I am using Alfresco Process Services and have created a created a spring boot project for custom logic like TaskListeners and Delegations. I am creating the jar file from this maven project and copying it into webapps/activiti-app/WEB-INF/lib folder.
I have a simple TaskListener as below which is getting called on Task start. But the #Autowired variables are always null.
package com.activiti.extension.bean;
#Component("myTaskListener")
public class MyTaskListener implements TaskListener {
#Autowired
UserService userService;
#Override
public void notify(DelegateTask task) {
logger.info("userService: " +userService); // Always prints null
}
Finally I was able to make it work. I was putting the task listener in the class field of the Task properties with full package name. Now I am putting Delegate expression like ${myTaskListener} and it worked...
Thank you all for your time and help
This is because your your MyTaskListener is annotated as #Component or at least being ignored by spring during init. for auto-wiring capabilities spring requires this annotation (or similar to this) under the provided #ComponentScan packages to consider the class as a bean otherwise it will take as a normal java class and hence the #autowired is of no use in your case.
This below code is worked for me
#Component
public class MyTaskListener implements TaskListener {
public static UserService getUserServiceObject() {
return SpringApplicationContextHolder.getApplicationContext().getBean(UserService.class);
}
#Override
public void notify(DelegateTask delegateTask) {
//UserService Object, It is not null now
getUserServiceObject();
}
}
#Component
public class SpringApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
There is also one more way to get to your custom service "UserService" using Alfresco Spring Application context.
First access ServiceRegistry (registry used for accessing Alfresco Services but also any other custom service):
ServiceRegistry serviceRegistry = (ServiceRegistry) Context.getProcessEngineConfiguration().getBeans().get(ActivitiConstants.SERVICE_REGISTRY_BEAN_KEY);
Then get custom service UserService:
QName qname = QName.createQName("UserService");
UserService userService = (UserService) serviceRegistry.getService(qname);
I have an issue that I would have thought I could resolve by now...
I'm writing a few simple tests to hit a couple services...
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
public class EndpointTests {
private static Logger log = Logger.getLogger(ApplicationController.class.getName());
#Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
#Mock
private ApplicationController applicationController = new ApplicationController();
static {
System.setProperty("audit.enabled", "false");
}
#BeforeClass
public static void setupProperties() {
System.setProperty("audit.enabled", "false");
}
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
#Test
public void contextLoads() {}
#Test
public void testGetApplications() throws Exception {
mockMvc.perform(get("/applications/")).andExpect(status().isOk())
.andDo(print())
.andExpect(content().contentType(TestUtils.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$.[0].name",is("XYZ")));
}
Long story short, I need this property disabled when running tests. I've tried setting the property in a static initializer and in #BeforeClass as I've seen on other posts but when it goes into the actual method, it's still its default 'enabled' value and the tests fail. I'm not using XML configuration so would prefer a code/annotation solution.
Any suggestions on another way I can fix this? Thanks.
UPDATE
Seems like every time my integration test runs:
#Test
public void testGetApplications() throws Exception {
mockMvc.perform(get("/applications/")).andExpect(status().isOk())
.andDo(print())
.andExpect(content().contentType(TestUtils.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$.[0].name",is("paasport-services")));
}
It executes my #Configuration classes on the call to mockMvc.perform...
#Configuration
public class AppConfiguration
...
...
So setting the property value in my test class does no good.
Is there any way to get in between and set this one property for my tests? I don't want to really create a separate test application context as it's just one property and everything has been working well for me up to this point.
Thanks.
I'm sure there's a much more elegant solution but I simply set a new system property in #BeforeClass on my test class.
My audit is handled by an aspect and I simply check that property I set only in my test class. If it's set to true, the advice doesn't execute.
I have a Spring JUnit tester class MySimpleTester:
#
RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={"classpath:/spring/mySimpleConfig.xml"})
public class MySimpleTester {
#Before
public void setUp() throws Exception {
myAdapter = (MyAdapter) applicationContext.getBean("myAdapter");
}
#test
public void testGetSimpleList() {
List<SimpleLink> simpleList = **myAdapter.getSimpleLinksList**();
}
...
...
In the adapter class I have:
public MyAdapter {
public List<SimpleLink> getSimpleLinksList() {
List<SimpleLink> simLinks = null;
String environment = AppFactory.getPropertiesObj();
...
...
class AppFactory implements ApplicationContextAware {
private static ApplicationContext context;
public void setApplicationContext(ApplicationContext acontext) {
context = acontext;
}
public getPropertiesObj() {
return getAppContext().getBean("propertiesBean");
}
I get NullPointerException and see that ApplicationContext is Null here.
However at the SpringJUnitTestRunner class MySimpleTester I could find the applicationContext to be initialized correctly. I am not including the mySimpleConfig.xml and included files. The method in MyAdapter class getSimpleLinksList() works perfectly fine from the web application when run in the application server, and the appcontext is obtained there.
Only from the Spring tester is it not able to reach the static application context AppFactory class, as it is called statically through AppFactory.getPropertiesObj(). I had the classpath set correctly as other test classes are executing.
If you want to access the current ApplicationContext in MySimpleTester:-
public class MySimpleTester {
#Autowired
ApplicationContext applicationContext;
#Before
public void setUp() throws Exception {
myAdapter = (MyAdapter) applicationContext.getBean("myAdapter");
}
#test
public void testGetSimpleList() {
List<SimpleLink> simpleList = **myAdapter.getSimpleLinksList**();
}
I think it is happening as multiple application contexts are created. The AplliCationContext object is supposed to be singleton. But when from the static method we call the applicationContext again it is refering to altogether different confirguration. The ApplicationContext is not even initialised there.
This does not happen when the same module is called from Spring MVC webcontanier. It happens only when you try to use Spring tester classes RunWith(SpringJUnit4ClassRunner.class). I can pass the AppContext in the business method but I do not want to change the bsiness method signature. I found some threads in spring community with similar issue.