How to Use JUnit to Test your JavaFX Applications with NetBeans IDE
Join the DZone community and get the full member experience.
Join For FreeIn fact, strangely enough, there's no support for tests; not even a specific folder to put them in. A few people blogged about some tricks to run JUnit in a JavaFX project, treating JUnit as a simple library; but this approach didn't satisfy me, also because I want something that produces the regular reports (e.g., to be used by Hudson) and doesn't require to write too much extra code (such as explicitly creating TestSuites); in a word, something that will allow me to have all the things working just as they are when JUnit tests will be available in NetBeans IDE.
I was able to do that, at the price of applying two small patches to JUnit. Here it is how to do in six steps.
1. Create a new JavaFX project and put a dependency on your primary project
You need to do that because you don't want to put test sources together with regular sources. You have to create a Dummy.fx class with a void run() function and declare it as the main class in the project properties: NetBeans treats every project as it was a complete application and wants an entry point.
public function run(): Void
{
}
You need to put the JAR of JUnit as a library of your test project - but a patched version of JUnit (see below).
2. Create the tests
Keep the usual conventions, such as putting a test in the same package as the fixture; name the test with the ****Test pattern and test methods test***(). Since in JavaFX there are no annotations, you have to use the JUnit 3.x approach, that is you have to extend TestCase. I'm including a sample of a real test from blueBill Mobile (which just a few not relevant stuff omitted for brevity). Note that to assert the equality of two sequences I had to write a small ad hoc function, as sequences aren't array and aren't known to JUnit. Such code (and other similar) should probably packed in a specific, small library.
package it.tidalwave.bluebillmfx.taxon.controller;
import java.lang.System;
import it.tidalwave.bluebillmfx.taxon.model.Taxon;
import it.tidalwave.bluebillmfx.taxon.model.TaxonomyMock;
import it.tidalwave.bluebillmfx.taxon.model.TaxonomyImpl;
import junit.framework.TestCase;
import org.junit.Assert;
public class TaxonSearchControllerTest extends TestCase
{
def mockTaxonomy = TaxonomyMock{};
def fullTaxonomy = TaxonomyImpl{};
postinit
{
def is = getClass().getResourceAsStream("EBNItalia2003.json");
fullTaxonomy.load(is);
is.close();
}
def fixture = InstrumentedTaxonSearchController
{
taxons: mockTaxonomy.species;
}
def performanceFixture = InstrumentedTaxonSearchController
{
taxons: fullTaxonomy.species;
}
public function testFunction(): Void
{
assertFilter("", ["Airone cinerino", "Airone bianco maggiore", "Airone rosso", "Piro piro", "Piro piro boschereccio"], -1, "");
assertFilter("A", ["Airone cinerino", "Airone bianco maggiore", "Airone rosso"], -1, "Airone ");
...
}
function assertFilter (filter : String, expected : String[], expectedIndex : Integer, expectedLeading : String)
{
fixture.filter = filter;
assertEquals(expected, displayNames(fixture.filteredTaxons));
assertEquals("fixture.selectedTaxonIndex", expectedIndex, fixture.selectedTaxonIndex);
assertEquals("fixture.leading", expectedLeading, fixture.leading);
}
function assertEquals (expected : String[], actual : String[]) : Void
{
Assert.assertTrue("{actual}", expected == actual);
}
}
3. Create a specific Ant target for testing
It is pretty much copied from similar stuff in NetBeans, just patched for getting the right stuff in the classpath (this could be done in a better way, but it presently works).
<target name="test" depends="init,compile">
<mkdir dir="${build.test.unit.results.dir}"/>
<junit showoutput="true" fork="true" failureproperty="tests.failed" errorproperty="tests.failed" filtertrace="${test.filter.trace}" tempdir="${build.test.unit.results.dir}">
<batchtest todir="${build.test.unit.results.dir}">
<fileset dir="${build.test.unit.classes.dir}">
<include name="**/*Test.class"/>
</fileset>
</batchtest>
<classpath>
<fileset dir="${platform.fxhome}/lib/shared">
<include name="*.jar"/>
</fileset>
<fileset dir="${platform.fxhome}/lib/desktop">
<include name="*.jar"/>
</fileset>
<pathelement path="${javac.classpath}"/>
<pathelement path="${build.classes.dir}"/>
<pathelement path="${build.test.unit.classes.dir}"/>
</classpath>
<!-- syspropertyset refid="test.unit.properties"/ -->
<jvmarg value="-ea"/>
<formatter type="brief" usefile="false"/>
<formatter type="xml"/>
</junit>
<fail if="tests.failed" unless="continue.after.failing.tests">Some tests failed; see details above.</fail>
</target>
4. Add a few properties in nbproject/project.properties
build.test.unit.classes.dir=${build.dir}/compiled
build.test.unit.results.dir=${build.dir}/test/results/
5. Patch JUnit
Now the most annoying part. Everything would already work, but for a detail: JUnit will complain about test classes having more than one constructor. Unfortunately, while JavaFX has got no constructors in the language, it creates synthetic code with two constructors in the bytecode (one with no parameters and one with a boolean parameter). I don't know what's it for, but for sure it makes JUnit complain. We have to patch JUnit. I downloaded JUnit 4.6 (I bet it's ok also 4.5) and patched these two files:
src/main/java/org/junit/runners/model/TestClass.java
src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java
diff TestClass.java TestClass.java.orig
31,33c31,33
< // if (klass != null && klass.getConstructors().length > 1)
< // throw new IllegalArgumentException(
< // "Test class can only have one constructor");
---
> if (klass != null && klass.getConstructors().length > 1)
> throw new IllegalArgumentException(
> "Test class can only have one constructor");
diff BlockJUnit4ClassRunner.java BlockJUnit4ClassRunner.java.orig
118,121c118,121
< // if (!hasOneConstructor()) {
< // String gripe= "Test class should have exactly one public constructor";
< // errors.add(new Exception(gripe));
< // }
---
> if (!hasOneConstructor()) {
> String gripe= "Test class should have exactly one public constructor";
> errors.add(new Exception(gripe));
> }
This basically removes the enforcement of a single constructor.
I believe that JUnit is rather extensible and one can specify his own runner. Perhaps one could write some code and embed it together with tests, rather than patching the original, but unfortunately configuring this stuff requires annotations, and in JavaFX no annotations...
Â
6. Create a small wrapper Ant script (for Hudson only)
Ok, at this time we have two NetBeans projects, the regular one and the one with tests. If you want to run this stuff under Hudson, put both of them into a directory and create this small script:
<?xml version="1.0" encoding="UTF-8"?>
<project name="blueBillMFX-global" default="hudson-fast" basedir=".">
<target name="hudson-fast">
<ant dir="blueBill-mobileFX" target="clean" inheritall="false"/>
<ant dir="blueBill-mobileFX" target="default" inheritall="false"/>
<ant dir="blueBill-mobileFXTest" target="clean" inheritall="false"/>
<ant dir="blueBill-mobileFXTest" target="test" inheritall="false"/>
</target>
</project>
Basically, the first call to ant might be redundant, as building the test project will make the first project to compile.
Ok, this can be surely improved, but in the meantime it just works, and you can start writing tests as they should be. The only important limitation it's that it won't work for a project configured with the Mobile profile, because of what sounds as a JavaFX compiler bug. My project is for the mobile profile, but I make sure that Hudson configures it as a desktop profile. I hope to fix this soon. Maybe in the meantime we will have NetBeans 6.7 final with JavaFX + JUnit support? Who knows.
What are you saying? Oh, you'd like to see an example project to try. Ok, blueBill Mobile isn't ready for primetime, but if you just want to look at how I configured tests you can check it out from Kenai:
svn co -r 141 https://kenai.com/svn/bluebill-mobile~svn/trunk/src/FX
Opinions expressed by DZone contributors are their own.
Comments