Annotated Step Configuration in Spring Batch, how to test - spring

I use spring batch with annotations only, I want to test a step which is configured like so:
#Bean("findMe")
#Qualifier("findMe")
public Step findMe() {
return stepBuilderFactory.get("findMe"). ... some step configuration
}
Test:
#Test
public shouldRunTheJob() {
JobLauncherTestUtils.launchJob("findMe");
}
I was not able to address the job, besides that I was able to test all other levels, how can I address a job annotated like this?

From what I understand from your question, you want to test a step and not a job.
Try using the following sample test class for your step test.
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = YourClassToTest.class)
public class StepTest {
#Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
#Test
public void testStep() throws Exception {
JobExecution jobExecution = jobLauncherTestUtils.launchStep("findMe");
// your test case, e.g. assert something on the jobExecution
}
}
For more information please refer to the spring batch docs here.

Related

Testing ApplicationListener with #SQL

We have a spring bean doing some initialization stuff at bootup of a spring-boot application. To do so, the application registers a ApplicationListener<ApplicationReadyEvent>, which queries the database and acts upon the data.
To test this process, we have to inject testdata in the database. Our first try was to init the database with a test-specific ApplicationListener<ApplicationStartedEvent>. This works, however, the code looks rather nasty. The idea was to use the #Sql annotation instead to load the initial data.
This works not as expected, as the data is injected after the ApplicationReadyEvent has been published. I was unable to find means to change the phase during which the #Sql data is written to the database.
Is there a way to ensure, the data of #Sql is written prior to publishing the ApplicationReadyEvent? The test is currently otherwise annotated to run with SpringRunner, #DataJpaTest and with #DirtiesContext.
Edit: Provide Code
The ApplicationListener ist provided as this:
#Component
public class ApplicationStartup implements ApplicationListener<ApplicationReadyEvent> {
#Override
public void onApplicationEvent(final ApplicationReadyEvent event) {
// Do someting with SQL-Data
}
}
While the test looks like this:
#RunWith(SpringRunner.class)
#ComponentScan
#Import(SomeTestConfig.class)
#DataJpaTest
//#SQL("/somedata.sql")
public class SomeTest {
#Test
public void test() {
// Assert ApplicationListener has run
}
}
With the test-config as follows:
#TestConfiguration
#Profile(ReplayTest.PROFILE_SOME_TEST)
class SomeTestConfig {
#Bean
public ApplicationListener<ApplicationStartedEvent> testSetupBean() {
return new ApplicationListener<ApplicationStartedEvent>() {
// Insert data within onApplicationEvent-Method
};
}
}
If I uncomment #SQL and comment-out the #Import, the testdata is visible from within the test itself but not from within the ApplicationListener.
You can make use of the #Order annotation. You can annotate your application listeners and your test with it. By default, this has lowest precedence. So you should provide higher precedence to your application listener and lowest to your test.
Listener:
#Override
#Order(Ordered.HIGHEST_PRECEDENCE)
public void onApplicationEvent(ApplicationReadyEvent event) {
System.out.println("...publishing application event");
}
Test:
#Test
#Sql(scripts = "test.sql")
#Order
public void test() {
System.out.println("...loading data");
}

SpringBatchTest multiple test classes - throws InstanceAlreadyExistsException

I am using #SpringBatchTest to run e2e tests on my Spring Batch application.
Everything works well except when I run both of my test classes (divided my tests into positive/negative test classes) together. The first one runs and tests pass, but the second fails trying to launch the context again. Since it is already launched, the tests fail on InstanceAlreadyExistsException.
Both my test classes are defined with the following annotations:
#RunWith(SpringRunner.class)
#SpringBatchTest
#EnableAutoConfiguration
#ContextConfiguration(classes = {MyTestConfiguration.class})
#TestExecutionListeners({MockitoTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class})
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
EDIT:
In general, what my test does is:
#RunWith(SpringRunner.class)
#SpringBatchTest
#EnableAutoConfiguration
#ContextConfiguration(classes = {HardDeleteTestConfiguration.class})
#TestExecutionListeners({MockitoTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class})
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class TestClass1 {
#Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
#Autowired
private JobRepositoryTestUtils jobRepositoryTestUtils;
#Before
public void setUp() {
jobRepositoryTestUtils.removeJobExecutions();
}
#Test
public void SpringBatchTest() {
// preparing data for test
// ...
JobExecution jobExecution =
jobLauncherTestUtils.launchJob(createJobParams("myKey","myValue"));
// Perform assertions
// ...
}
}
private void createJobParams(String key, value) {
JobParameters uniqueJobParameters = jobLauncherTestUtils.getUniqueJobParameters();
JobParametersBuilder paramsBuilder = new JobParametersBuilder(uniqueJobParameters);
paramsBuilder.addString(key, value);
return paramsBuilder.toJobParameters();
}
}
TestClass2 is the same as TestClass1 with only different data preparation and assertions.
Also my test properties are as follows:
# Spring Boot configuration
spring.main.allow-bean-definition-overriding=true
spring.batch.job.enabled=false
# Spring Batch configuration
spring.batch.job.names=myBatchJob
I have tried all combinations of true and false for the previous flags but it does not make any difference.
Since it is already launched, the tests fail on InstanceAlreadyExistsException.
This means the datasource is reused between tests, and when the second test runs, it will try to launch the same job instance.
In your createJobParameters() method, you can use JobLauncherTestUtils#getUniqueJobParameters to create unique job parameters and run a different job instance for each test.
Eventually we realized it was an in-house framework wrapping Spring that caused the problem (stupid static instantiations on context loading and such).
To solve we used #MockBean on one problematic class, and #EnableAutoConfiguration( exclude = ProblematicConfiguration.class) in the annotation located above the test class.

Mock an Autowired ExecutorService

Abstract:
I have a Spring #Component that uses an autowired ExecutorService as a work pool. I'm using JUnit and Mockito to test the functionality of the component and I need to mock that Executor Service. This has been trivial for other autowired members - a generic helper, and a DAO layer for instance are easily mocked, but I need a real Executor Service.
Code:
#RunWith(MockitoJUnitRunner.class)
public class MadeUpClassNameTest{
#Mock
private ExecutorService executor;
#Before
public void initExecutor() throws Exception{
executor = Executors.newFixedThreadPool(2);
}
#InjectMocks
private ASDF componentBeingAutowired;
...
This alone doesn't work, the results of invokeAll() is always an empty list.
Attempting to more explicitly mock the executor method also doesn't work...
#Test
public void myTestMethod(){
when(executor.invokeAll(anyCollection()))
.thenCallRealMethod();
...
}
I get the cryptically worded exception:
You cannot use argument matchers outside of verification or stubbing.
(I thought this was a stubbing ?)
I could provide a thenReturn(Answer<>) method, but I'd like to make sure that the code actually works with an executor, a fair bit of the code is devoted to mapping the results of Futures.
Problem
How do I provide a real (or functionally usable mock) Executor Service ? Alternatively, is my difficulty in testing this component a sign that this is a poor design in need of refactoring, or possibly a bad test scenario ?
Notes
I want to stress that my problem is NOT getting Mockito or Junit set up. Other mocks and tests work correctly. My problem is specific only to the particular mock above.
Using: Junit 4.12, Mockito 1.10.19, Hamcrest 1.3
I think the following code runs after the Mock is injected.
#Before
public void initExecutor() throws Exception{
executor = Executors.newFixedThreadPool(2);
}
This causes your local copy of executor to be set, but not the one that is injected.
I would recommend using constructor injection in on your componentBeingAutowired and create a new one in your unit test and exclude Spring dependencies. Your test could then look like something below:
public class MadeUpClassNameTest {
private ExecutorService executor;
#Before
public void initExecutor() throws Exception {
executor = Executors.newFixedThreadPool(2);
}
#Test
public void test() {
ASDF componentBeingTested = new ASDF(executor);
... do tests
}
}
Another way is to use ReflectionTestUtils to inject the executor
#Before
public void initExecutor() {
ReflectionTestUtils.setField(componentBeingAutowired, "executor", Executors.newFixedThreadPool(2);
}
You can use the #Spy annotation.
#RunWith(MockitoJUnitRunner.class)
public class MadeUpClassNameTest{
#Spy
private final ExecutorService executor = Executors.newFixedThreadPool(2);
....
}
You can use the #Spy annotation.
#RunWith(MockitoJUnitRunner.class)
public class MadeUpClassNameTest{
#Spy
private final ExecutorService executor = Executors.newFixedThreadPool(2);
#Test
...
}

Using #SpringApplicationConfiguration: How to set jobparameters in tests when using spring-batch and spring-boot

Is there a way, how I can define jobparameters in integration test, when using pure spring-boot with spring-batch?
When I define a simple Batch-Job in Spring-Boot and start it with SpringApplication.run(args), I can pass my program-arguments in the run method.
Since I have Spring-Boot-Batch activated, those arguments are converted into JobParameters and are then passed to the job. This happens inside the class JobLauncherCommandLineRunner.
Afterwards, I can read this jobparameters via the jobexecution object.
Now, I would like to write a test, which starts a job almost the same way, as it will be in production. Therefore, I'm using #SpringApplicationConfiguration and write a test like the following one:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {MyBatchRootConfiguration.class})
#IntegrationTest({"aProp=aValue"})
public class MyBatchTestClass {
#Test
public void launchTest() {
}
}
Note: MyBatchRootConfiguration defines the job and also adds the #SpringBootApplication, #EnableBatchProcessing and so on.
The problem I'm facing now: how can I pass arguments that will be converted to jobparameters? Is there a way, how this could be done?
As far as I have seen, the #SpringApplicationConfiguration defines the loader as "SpringApplicationContextLoader". Therefore, adding #SpringApplicationConfiguration to a test class eventually calls the method SpringApplicationContextLoader.loadContext(...). In this method, the SpringContext is created and an instance of SpringApplication is also created. Finally, in the following line of SpringApplicationContextLoader.loadContext() (line 103 for version 1.2.4-RELEASE of spring-boot)
ConfigurableApplicationContext applicationContext = application.run();
run is called.
But contrary to the example of spring-boot-batch on https://spring.io/guides/gs/batch-processing/
#SpringBootApplication
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
which in the end also calls the non-static run method of SpringApplication, there is no way to pass or to define args.
But this is crucial for spring-batch, since these argsare converted to JobParameters in JobLauncherCommandLineRunner.
(SpringApplication.run(String ... args) calls SpringAppplication.afterRefresh(..., String[] args)calls SpringApplication.runCommandLineRunners(..., String[] args)calls JobLauncherCommandLineRunner.run(String ... args))
I guess there is no other way than adapting the JobLauncherCommandLinerRunner, or using it directly as in the following example:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {MyBatchRootConfiguration.class})
#IntegrationTest({"aProp=aValue"})
public class MyBatchTestClass {
#Autowired
private JobLauncherCommandLineRunner runner;
#Test
public void launchTest() {
String[] myArgs = new String[]{"jobParam1=value1"};
runner.run(myArgs);
}
}
I know, that I can use the JobLauncherTestUtils, but what if I would like to have a test, that starts a job "the spring-boot way"? #SpringApplicationConfiguration is exactly for that.
But if we liked to use it to launch a Spring-Batch job, it looks to me as if we are missing a way to define the program-arguments and therefore to define jobparameters.
This leads me to the conclusion, that you cannot use #SpringApplicationConfiguration to test a job, if you have a job that depends on jobparameters.
Are there any ideas or solutions?
Thanks
Hansjoerg

How do i write Junit4 tests without Spring transactional test support?

I would like to test transaction rollbacks in my application service. As a result i do not want to use spring's AbstractTransactionalJUnit4SpringContextTests with the #Transactional annotation as that wraps my test method in a transaction.
I know spring also offers AbstractJUnit4SpringContextTests for tests without transactions. However i need to have some transactions to save data into my database and also query data for assertions after running the service under test.
How can i write a Junit 4 spring transactional test without the default transaction test management?
Here is the answer to your question ;
#ContextConfiguration(value = "/applicationContext.xml")
public class JPASpringTest extends AbstractJUnit4SpringContextTests {
#PersistenceContext(unitName="jpadenemelocal")
EntityManager entityManager;
#Autowired
protected PlatformTransactionManager transactionManager;
#Test
public void testInsertRolManyToMany() {
TransactionStatus status = transactionManager.getTransaction(null);
// your code
transactionManager.commit(status);
}
}
The spring docs should cover this in their testing chapter
What you might want is to configure your test without the default TransactionalTestExecutionListener like this
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#TestExecutionListeners({DependencyInjectionTestExecutionListener.class,DirtiesContextTestExecutionListener.class})
public class SimpleTest {
#Test
public void testMethod() {
// execute test logic...
}
}

Resources