Flux Unit (functional, integration: make sure it works) Testing
Introduction
We take testing very seriously here @ Flux. Testing complex enterprise systems is not for the faint of heart as it can become very complicated in short order. Designing unit tests, functional tests, integration tests, user interface tests, and in-container tests (inside of enterprise application containers or a simple servlet container) are enough to make your head spin; much less keeping the systems up and running (not many QA teams have dedicated System and Database Administrators).
You may have landed on this blog post because you are considering building some automated tests to ensure that your enterprise software which uses Flux is up-to-snuff and enterprise-ready. No worries. Let’s keep it simple. We’ll start out by looking out some Java code which can be used as a starting point for your Flux related tests. The test code that we’ll consider today will cover unit, functional, and integration tests (as I like to call them: make sure it works tests). We’ll save in-container testing and user interface testing for another day.
Testing Environment
What’s required in a Flux testing environment? We’ll use JUnit (http://www.junit.org/), Apache log4j (http://logging.apache.org/log4j/), and of course Flux (http://fluxcorp.com/). You can use Flux with a lightweight in-memory database (H2: http://www.h2database.com/) or you can configure Flux with the database that you’ll use in production (or to test with multiple databases if you are an OEM embedding Flux in an application that supports multiple databases such as Oracle, SQL Server, DB2, MySQL, or PostgreSQL).
Abstract Flux Test
Let’s start by creating an abstract test which can be extended to create a Flux test for your application. Let’s call this AbstractFluxTest.java (a very sensible name). This abstract test will take care of initializing Flux, setting up the test, cleaning up after the test, and provide some convenience methods for things like loading flow charts from XML files (.FFC) and verifying that Flux flow charts and actions executed as expected.
AbstractFluxTest.java
import flux.*;
import flux.xml.XmlEngineHelper;
import flux.xml.XmlFactory;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import java.io.FileInputStream;
import java.util.Date;
import java.util.List;
public abstract class AbstractFluxTest {
protected Factory factory = Factory.makeInstance();
protected EngineHelper engineHelper = factory.makeEngineHelper();
protected Engine engine;
protected String enginePropertiesFile = "engine.properties";
protected boolean clearEngine = false;
protected Date startTime;
public static boolean fired = false;
public static final Logger log = Logger.getLogger("flux-test");
@Before
public void setUpTest() throws Exception {
log.info("[START] AbstractFluxTest.setUpTest");
startTime = new Date();
engine = factory.makeEngine(enginePropertiesFile);
if (clearEngine) {
engine.clear();
}
engine.start();
log.info("[END] AbstractFluxTest.setUpTest");
}
@After
public void tearDownTest() throws Exception {
log.info("[START] AbstractFluxTest.tearDownTest");
fired = false;
engine.dispose();
log.info("[END] AbstractFluxTest.tearDownTest");
}
@SuppressWarnings("unchecked")
protected FlowChart readFlowChartFFC(String fileName) throws Exception {
XmlEngineHelper xmlHelper = XmlFactory.makeInstance().makeXmlEngineHelper();
FileInputStream fileInputStream = new FileInputStream(fileName);
try {
List<FlowChart> flowCharts = xmlHelper.makeFlowChartsFromXml(fileInputStream, false);
return flowCharts.get(0);
} finally {
fileInputStream.close();
}
}
protected FlowChart readFlowChartFFCOnClassPath(String filename) throws Exception {
return readFlowChartFFC(this.getClass().getClassLoader().getResource(filename).getPath());
}
protected boolean didActionExecute(String namespace, String actionName) throws Exception {
log.info("didActionExecute namespace: " + namespace + " actionName: " + actionName);
ActionHistoryIterator it = engine.getActionHistory(namespace, actionName, startTime, new Date());
try {
return it.next() != null;
} catch (Exception e) {
return false;
} finally {
it.close();
}
}
public long waitForRuns(String namespace, int minimumExpectedRunCount, int delayInSeconds, int timeoutInSeconds) throws Exception {
long runCount = 0;
long totalTime = 0;
int delayInMillis = delayInSeconds * 1000;
String initialMsg = "waitForRuns namespace:" + namespace + " minimumExpectedRunCount: " + minimumExpectedRunCount + " delayInSeconds: " + delayInSeconds +
" timeoutInSeconds: " + timeoutInSeconds;
log.info(initialMsg);
while (runCount < minimumExpectedRunCount && totalTime < timeoutInSeconds) {
runCount = engine.getRunCount(namespace);
log.info(initialMsg + " runCount: " + runCount + " totalTime: " + totalTime);
if (runCount < minimumExpectedRunCount) {
Thread.sleep(delayInMillis);
totalTime += delayInSeconds;
}
}
return runCount;
}
}
Flux Engine Configuration
AbstractFluxTest loads the Flux engine configuration from the file: engine.properties. You can add more configuration properties to match your production environment. Here’s the recommended minimum configuration for a testing environment:
engine.properties
database_type=H2 logger_type=log4j
Apache log4j Configuration
I’ve decided to use Apache log4j for logging in these tests. I’ve chosen log4j for two reasons: it’s popular and Flux can tie into log4j (you can configure Flux and your test code to log to the same or different files and some other crazy things). Here’s a simple log4j configuration that will log the test messages (logged via log.info() in the test code) to both the console and the flux-test.log file (where the Flux engine is configured to log to as well via the engine.properties file: logger_type=log4j).
log4j.rootLogger=INFO,FileAppender log4j.logger.flux-test=INFO,ConsoleAppender log4j.appender.ConsoleAppender.Threshold=INFO log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender log4j.appender.ConsoleAppender.layout=org.apache.log4j.PatternLayout log4j.appender.ConsoleAppender.layout.ConversionPattern=%d %-4r %-5p [%t] %10c %3x - %m%n\ log4j.appender.FileAppender=org.apache.log4j.RollingFileAppender log4j.appender.FileAppender.MaxFileSize=50MB log4j.appender.FileAppender.MaxBackupIndex=10 log4j.appender.FileAppender.File=flux-test.log log4j.appender.FileAppender.layout=org.apache.log4j.PatternLayout log4j.appender.FileAppender.layout.ConversionPattern=%d %-4r %-5p [%t] %15c %3x - %m%n\
Sample Flux Test
Now that we have the test framework in place, let’s take a look at a simple Flux test.
FluxTest.java
import flux.FlowChart;
import flux.JavaAction;
import flux.TimerTrigger;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
public class FluxTest extends AbstractFluxTest {
@Test
public void testFFCFile() throws Exception {
String namespace = "/FluxFlowChart"; log.info("[START] FluxTest.testFFCFile");
FlowChart flowChart = readFlowChartFFC("flowCharts/FluxFlowChart.ffc");
flowChart.setName(namespace);
engine.put(flowChart);
waitForRuns(namespace, 1, 10, 120);
didActionExecute("/FluxFlowChart", "Timer Trigger");
didActionExecute("/FluxFlowChart", "Java Action");
assertTrue(fired);
log.info("[END] FluxTest.testFFCFile");
}
@Test
public void testJavaAPI() throws Exception {
log.info("[START] FluxTest.testJavaAPI");
String namespace = "/FluxFlowChart";
FlowChart flowChart = engineHelper.makeFlowChart(namespace);
TimerTrigger timerTrigger = flowChart.makeTimerTrigger("Timer Trigger");
timerTrigger.setTimeExpression("+1m");
JavaAction javaAction = flowChart.makeJavaAction("Java Action");
javaAction.setListener(FluxTestListener.class);
timerTrigger.addFlow(javaAction);
engine.put(flowChart);
waitForRuns(namespace, 1, 10, 120);
didActionExecute("/FluxFlowChart", "Timer Trigger");
didActionExecute("/FluxFlowChart", "Java Action");
assertTrue(fired);
log.info("[END] FluxTest.testJavaAPI");
}
}
FluxTest.java provides to tests: one for testing a flow chart which was designed using the visual Flux flow chart designer and another that creates the flow chart to test using the Flux Java API. In testFFCFile(), we load the flow chart from the file FluxFlowChart.ffc, ensure that the name is set correctly, and verify that the actions and Java code executed as expected.
Here’s what the flow chart looks like that is loaded from FluxFlowChart.ffc:

Flux Test Listener
Many Flux customers use Java Action’s in their flow charts. A Java Action allows flow charts (which are often designed visually by connecting various components together with conditions) to execute some Java code. in FluxTest above, we designed a flow chart that executes FluxTestListener. For illustration purposes, this listener code is designed to update a variable in the test case which can be asserted via JUnit after the flow chart completes. This illustrates how we can ensure that the actual Java code executed as anticipated.
FluxTestListener.java
import flux.ActionListener;
import flux.KeyFlowContext;
public class FluxTestListener implements ActionListener {
public Object actionFired(KeyFlowContext flowContext) throws Exception {
FluxTest.fired = true;
flowContext.getLogger().info("FluxTestListener.actionFired!");
return null;
}
}
Conclusion
Well, that should give you enough to get you started testing with Flux. I’ll follow-up with some more Flux test related blog posts (maybe detecting errors from the Flux engine via Apache log4j will be next). If you have questions about testing your Flux based software or would like more code examples, just reach out to Flux Support and we’ll be sure to satisfy your Flux testing appetite. Have fun!
Posted on September 19, 2011, in Uncategorized. Bookmark the permalink. 5 Comments.

Great article.
Is it possible to mock out certain actions or triggers? Let me go over a scenario
I have a flowchart that starts with a file exists trigger which waits for a file over ftp then that file is processed through a java action which would return 0 or 1 depending on the file being “good” or “bad”. If the file is good then we call a rest service to get some more information call a process action and end if the file is bad print a message on the console and end. Now to test this flowchart I want to mock out the following
1) File exists trigger such that instead of waiting on ftp I supply the file from the local machine.
2) Rest action so that instead of making a call to a webservice we supply a local xml file.
3) If lets say the process action takes an hour to complete mock it out as well
We would like to load the flowchart from the file system for the unit test and I am hoping to reuse most of the information on the flowchart e.g. flows, runtime datamaps, checkpoints etc.
Thanks! It’s possible to load a flow chart and change it’s values via Java code. We do this in our test suite. For example, we often ask customers for their actual flow charts. Their flow charts can’t run out-of-the-box in our environment so we have to modify them. However, we keep the original flow chart received from the customer in-tact and perform as few modifications as possible to allow it to run in our environment and faster (reduce time delays) via Java code on-the-fly.
The AbstractFluxTest.readFlowChartFFC() method above illustrates how to read a flow chart from a file on disk. Once loaded, the flow chart is available as a flux.FlowChart object. This allows you to access any triggers, actions, flows, etc. and modify them via the Flux Java API.
That’s easy. Now for the difficult part. What to change and what to change it to? For your case of a File Exist Trigger watching an FTP server, I would suggest using the embeddable 100% Java Apache FTP Server (http://mina.apache.org/ftpserver/). This will allow you to test as much of the Flux functionality as possible. Furthermore, if you use the Flux runtime configuration properties file to set your FTP server properties in the File Exist Trigger (like: ${runtime hostname}), then you don’t even need to modify the values via the Java API but rather just have a different runtime configuration file for the test and production environments (i.e., runtime-test.properties may point to localhost for the FTP server).
For mocking the REST Action, again I would suggest embedding an HTTP server that can return serve the HTML file and again use runtime configuration properties with different values for the test and production configurations. This will allow you to make sure Flux is invoking a REST service and returning the results correctly (albeit not the production REST server, but close).
And for the Process Action, again, I’d use the runtime configuration properties to reference a different script/batch file in the test and production environments.
If you’re not keen on my recommended approach.. an alternative is to use the Java API, after loading the flow chart, to replace the File Exist, REST Action, and Process Action properties with a Java Action that does the returns the expected result. This is doable, but probably not a trivial task (flows and runtime data maps could be difficult to deal with when swapping out triggers/actions for Java Actions). IMOHO, I don’t prefer this approach because it requires more modifications to the flow chart for testing purposes than my recommended approach.
We have tests that embed the Apache FTP Server and also an HTTP server with mock REST resources. I can provide code examples for that if you’re interested in pursuing the embedded mock server route. If you’re interested in that, just let me know how pressing it is for you and I’ll schedule some time to post some example code for you.
Thanks for the quick response. I like your idea about embedding an ftp/http server. I would be interested in the code examples. We are not in a hurry so please roll out the examples at your own pace.
Sticky note applied to monitor so I won’t forget: http://screencast.com/t/wwoccX0aP4
Pingback: Blogging at the speed of thought » Invoking web services from your Flux workflow