How to write integration tests for spring-batch-integration? - spring-batch

I'm using spring-integration bundled with spring-batch and got stuck trying to write integration tests to test the whole flow, not just single config.
I've created Embedded Sftp Server for this tests and trying to send message to sftpInboundChannel - the message is sent, but nothing happens, but when i send this message to the next channel (after sftpInboundChannel) it goes ok. Also i'm not able to load test source properties, even though i'm using #TestPropertySource annotation.
This are my class annotations
#TestPropertySource(properties = {
//here goes all the properties
})
#EnableConfigurationProperties
#RunWith(SpringRunner.class)
#Import({TestConfig.class, SessionConfig.class})
#ActiveProfiles("it")
#SpringIntegrationTest
#EnableIntegration
#SpringBootTest
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
This is my class body
#Autowired
private PollableChannel sftpInboundChannel;
#Autowired
private SessionFactory<ChannelSftp.LsEntry> defaultSftpSessionFactory;
#Autowired
private EmbeddedSftpServer server;
#Test
public void shouldDoSmth() {
RemoteFileTemplate<ChannelSftp.LsEntry> template;
try {
template = new RemoteFileTemplate<>(defaultSftpSessionFactory);
SftpTestUtils.moveToRemoteFolder(template);
final List<ChannelSftp.LsEntry> movedFiles = SftpTestUtils.listFilesFromDirectory("folder/subfolder", template);
log.info("Moved file {}", movedFiles.size());
final MessageBuilder<String> messageBuilder = MessageBuilder.withPayload("Sample.txt") // path to file
.setHeader("file_Path", "Sample.txt")
boolean wasSent = this.sftpInboundChannel.send(messageBuilder.build());
log.info("Was sent to sftpInboundChannel channel {}", wasSent);
log.info("message {}", messageBuilder.build());
} finally {
SftpTestUtils.cleanUp();
}
}

To the case of not read the property file one solution is add in your Test class something like this:
#BeforeClass
public static void beforeClass() {
System.setProperty("propertyfile", "nameOfFile.properties");
}
A second way is to create a xml (or class) config where you add the tag:
<context:property-placeholder
location="nameOfFile.properties"
ignore-resource-not-found="true" system-properties-mode="OVERRIDE" />
and your file will be localized.
The property file should be inside of resources folder.

Related

Integration testing for spring cloud stream, how to ensure application resources are not used

I'm trying to do some integration testing for my cloud streaming application. One of the main issues I'm observing so far is that the TestChannelBinderConfiguration keeps picking up the configuration specified in src/main/java/resources/application.yml instead of keeping it as blank (since there is no config file in /src/test/resources/).
If I delete the application.yml file or remove all spring-cloud-stream related configuration, the test passes. How can I ensure that the TestChannelBinderConfiguration does not pick up application.yml file.
#Test
public void echoTransformTest() {
try (ConfigurableApplicationContext context =
new SpringApplicationBuilder(
TestChannelBinderConfiguration.getCompleteConfiguration(DataflowApplication.class))
.properties(new Properties())
.run("--spring.cloud.function.definition=echo")) {
InputDestination source = context.getBean(InputDestination.class);
OutputDestination target = context.getBean(OutputDestination.class);
GenericMessage<byte[]> inputMessage = new GenericMessage<>("hello".getBytes());
source.send(inputMessage);
assertThat(target.receive().getPayload()).isEqualTo("hello".getBytes());
}
}
I resolved this by doing the following:
SpringBootTest(properties = {"spring.cloud.stream.function.definition=reverse"})
#Import(TestChannelBinderConfiguration.class)
public class EchoTransformerTest {
#Autowired private InputDestination input;
#Autowired private OutputDestination output;
#Test
public void testTransformer() {
this.input.send(new GenericMessage<byte[]>("hello".getBytes()));
assertThat(output.receive().getPayload()).isEqualTo("olleh".getBytes());
}
}
and adding an application.yml to the test/resources this ensures that we don't read the src/resources application properties.
Another way was to explicitly define
#TestPropertySource(locations= "test.yml")

Reading file dynamically in spring-batch

I am trying to transfer any files (video,txt etc) between different endpoints (pc, s3, dropbox, google drive) using spring-batch on a network. For that, I am getting json file containing list of files location(url) to be transferred (assume I can access those location).
So, how do I tell the reader to read the input once my controller is hit (in which job is created) and not at the time of starting spring-boot application?
I have tried adding "spring.batch.job.enabled=false" which stops spring-batch to start automatically but my concern is where should I write setting my resource line that will be provided to ItemReader :
FlatFileItemReader<String> reader = new FlatFileItemReader<String>();
reader.setResource(someResource);
Because during setting resources I am getting NullPointerException.
The Running Jobs from within a Web Container explains that with a code example. Here is an except:
#Controller
public class JobLauncherController {
#Autowired
JobLauncher jobLauncher;
#Autowired
Job job;
#RequestMapping("/jobLauncher.html")
public void handle() throws Exception{
jobLauncher.run(job, new JobParameters());
}
}
In your case, you need to extract the file name from the request and pass it as a job parameter, something like:
#RequestMapping("/jobLauncher.html")
public void handle() throws Exception{
URL url = // extract url from request
JobParameters parameters = new JobParametersBuilder()
.addString("url", url)
.toJobParameters();
jobLauncher.run(job, parameters);
}
Then make your reader step-scoped and dynamically extract the file from job parameters:
#StepScope
#Bean
public FlatFileItemReader flatFileItemReader(#Value("#{jobParameters['url']}") URL url) {
return new FlatFileItemReaderBuilder<String>()
.resource(new UrlResource(url))
// set other properties
.build();
}
This is explained in the Late Binding of Job and Step Attributes section.

Is it possible to apply xml Templates in JAVA DSL Runner

we have a lot of old citrus xml Testcases and templates in our Projects. After Upgrading to newer version I decided to make the switch to Java DSL. Is it possible to keep using the old templates? If i Try to do so, I get a "No bean named .. is defined" exception.
I tried the to import the template file via #ImportResource but without success.
You can write a simple custom test action that loads the template and executes it with current test context:
Given the following template in templates/hello-template.xml
<spring:beans xmlns="http://www.citrusframework.org/schema/testcase"
xmlns:spring="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.citrusframework.org/schema/testcase http://www.citrusframework.org/schema/testcase/citrus-testcase.xsd">
<template name="helloTemplate">
<echo>
<message>Hello ${user}</message>
</echo>
</template>
</spring:beans>
You can write a custom test action for loading that template:
public class TemplateTest extends TestNGCitrusTestRunner {
#Test
#CitrusTest
public void test() {
run(new CallTemplateAction("templates/hello-template.xml", "helloTemplate"));
}
private class CallTemplateAction extends AbstractTestAction {
private final String templateName;
private final String templateLocation;
public CallTemplateAction(String templateLocation, String templateName) {
this.templateLocation = templateLocation;
this.templateName = templateName;
}
#Override
public void doExecute(TestContext testContext) {
Template template = new ClassPathXmlApplicationContext(new String[] { templateLocation },
testContext.getApplicationContext())
.getBean(templateName, Template.class);
template.getParameter().put("user", "foo");
template.execute(testContext);
}
}
}
You should probably cache the template instance and/or close the application context when done with the action.

NLog callback possible?

I am in the process of converting a home grown logging system to NLog and am wondering if there is a way to add an event to a Logger or otherwise support a mechanism where when I log a message I can get a callback with the final formatted message and the LogLevel. I currently use something like this to send server messages back to a connected client.
Thx
This is an MCVE of what I was talking about in the comments. Create a target that accepts some callback functions:
[Target("MyFirst")]
public sealed class MyFirstTarget : TargetWithLayout
{
private readonly Action<string>[] _callbacks;
public MyFirstTarget(params Action<string>[] callbacks)
{
_callbacks = callbacks;
}
protected override void Write(LogEventInfo logEvent)
{
foreach (var callback in _callbacks)
{
callback(logEvent.FormattedMessage);
}
}
}
Configure NLog to use the target. I do this programmatically since the callbacks are passed in the constructor. You can also configure the target in the NLog.config, but your target will need to be a singleton then so you can register the callbacks in code.
class Program
{
public static void Main()
{
LogManager.Configuration.AddTarget("MyFirst", new MyFirstTarget(s => Debug.WriteLine(s)));
var logger = LogManager.GetCurrentClassLogger();
logger.Debug("test");
}
}
With no other NLog configuration (copy this code into an empty project and add the NLog nuget package), this will emit a message to your debug window.

Why am I having trouble accessing a .properties file in a standalone instance of tomcat but not in an eclipse-embedded instance?

I wrote a simple Hello World Servlet in Eclipse containing the following in the doGet method of my HelloWorldServlet.java
PrintWriter writer = response.getWriter();
String hello = PropertyLoader.bundle.getProperty("hello");
writer.append(hello);
writer.flush();
PropertyLoader is a simple class in the same package as the Servlet that does the following:
public class PropertyLoader {
public static final Properties bundle = new Properties();
static {
InputStream stream = null;
URL url = PropertyLoader.class.getResource("/helloSettings.properties");
stream = new FileInputStream(url.getFile());
bundle.load(stream);
}
}//End of class
I placed a file called helloSettings.properties in /WebContent/WEB-IND/classes that contains the following single line of content:
hello=Hello Settings World
When I add Tomcat 6.0 to my project and run it in eclipse it successfully prints
"Hello Settings World" to the web browser.
However when I export the project as a war file and manually place it in
.../Tomcat 6.0/webapps I then get "null" as my result.
Is it a problem with the classpath/classloader configuration? permissions? any of the other configuration files? I know for a fact that the helloSettings.properties file is in the WEB-INF/classes folder.
Any help?
Well, after much browsing I found what seems a "normal" why to do what I'm trying to do:
Instead of...(how I was doing it)
public class PropertyLoader {
public static final Properties bundle = new Properties();
static {
InputStream stream = null;
URL url = PropertyLoader.class.getResource("/helloSettings.properties");
stream = new FileInputStream(url.getFile());
bundle.load(stream);
}
}//End of class
THE FIX
public class PropertyLoader {
public static final Properties bundle = new Properties();
static {
InputStream stream = null;
stream = SBOConstants.class.getResourceAsStream("/sbonline.properties");
bundle.load(stream);
}
}//End of class
I'm modifiying someone else's code so I'm not sure why they did it the other way in the first place... but I guess url.getFile() was my problem and I don't know why.