ant and junit5 – simulating sysproperty

In JUnit 5, you use the junitlauncher Ant task rather than the junit Ant task. Unfortunately, this isn’t a drop in replacement. For example, junitlauncher doesn’t offer the option to fork and run the JUnit tests. As a result, it also doesn’t have the nested sysproperty tag so you can pass system properties. This is a problem.

For the CodeRanch, we set a system property for the default file encoding. Since developers are around the world, we can’t assume everyone “just has” the encoding set.

Disclaimer

Since JUnit 5 functionality for Ant was introduced this year, I’m hoping what I did in this post is a short term workaround.

Option 1 – pass to Ant

You can pass the the properties to Ant itself as described on Stack Overflow. For example:

__JAVA_OPTIONS=-Dfile.encoding=ISO8859_1

pros:

  • simple

cons:

  • subpar – all the developers need to remember to do this. The reason we had it in Ant in the first place is so folks wouldn’t need to remember
  • for some use cases, the desired system properties could be derived and not know when calling Ant

Option 2- Nashorn Code

Since JUnit 5 is being run in the same process as Ant itself, you need to set the system property in memory. Luckily, Ant allows you to run scripting in various languages. I chose Nashorn because it is built into Java. (There are other variants of this; see below)

<script language="javascript">
  <![CDATA[
    var imports = new JavaImporter(java.lang.System);
    imports.System.setProperty('file.encoding', 'ISO8859_1')
  ]]>
</script>

Pros

  • Short and simple
  • Nashorn is deprecated for removal starting Java 11. This means at some point, it can be removed. (I’m hopeful that the Ant task itself will support system properties by then.

Cons

  • Requires Java 8 or higher
  • The System property is set for the remainder of the build (you could write another code block to null it out after the test if this is a problem)

Variants of option 2

If you are running Ant with a version of Java below Java 8, you could use this technique, but use Rhino instead. I didn’t test this, but I think the code is


importClass(java.lang.System);
System.setProperty('file.encoding', 'ISO8859_1')


JUnit 5 itself uses Java 8 so nobody would be in the situation of pre-Java 8 and trying to use this blog post.

If you are running Ant with a version of Java where Nashorn has been removed, you could use Groovy or Jython as the embedded language. The code is simpler. I didn’t chose this because it requires adding another jar to the Ant directory. I prefer to minimize these set up extensions. Especially for a feature like this which is likely to be temporary.

System.setProperty('file.encoding', 'ISO8859_1')

JUnit 5 – assertAll

I gave a half day JUnit 5 workshop at DevNexus last week. When I covered assertAll(), an interesting piece of code came up. In JUnit 5, there is an assertAll() which runs all of the assertions it contains so you can see all of them that failed at once.  This replaces the need for AssertJ’s SoftAssertions.

So how many assertions do you think this will fail with?

@Test
public void all() {
  assertAll("message", () -> {
    assertEquals(1, 2);
    assertEquals(3, 4);
  });
}

The fact that this is a blog post should get you nice and suspicious. The answer is one:

org.opentest4j.MultipleFailuresError: message (1 failure)
expected: <1> but was: <2>

Why is this? Well, remember that assertAll takes a vararg of Executables (lambdas in this case.) However, the above code only has one lambda. So JUnit runs the two asserts in the same block which makes them happen sequentailly.

To fix this, you need to pass separate lambdas for each assertions.

@Test
public void all() {
  assertAll("message", 
	() -> assertEquals(1, 2), 
	() -> assertEquals(3, 4));
  }
}

Ah this one tells you about both failures.

org.opentest4j.MultipleFailuresError: message (2 failures)
expected: <1> but was: <2>
expected: <3> but was: <4>

Thanks to Steve Kiley for asking the question that led to this blog post!

junit 5, gradle and containers

Up until recently, I’ve been using JUnit 5 with Eclipse and Maven. As I started using it with Gradle, I became frustrated that you couldn’t see the number of tests run when the tests succeeded. (Don’t worry; this is an easily solvable problem.)

First JUnit 4

You can add code to have Groovy print the number of tests. This matches the number of tests that Eclipse/Maven say are run.

test {
   afterSuite { desc, result ->
    	     if (!desc.parent)
        		println("${result.resultType} " +
            "(${result.testCount} tests, " +
            "${result.successfulTestCount} successes, " +
            "${result.failedTestCount} failures, " +
            "${result.skippedTestCount} skipped)")
   }
}

On to JUnit 5
It’s even less code to ask JUnit 5 to print the test results:

junitPlatform {
  details 'tree'
}

This prints something like

Test run finished after 2518 ms
 [ 20 containers found ]
 [ 0 containers skipped ]
 [ 20 containers started ]
 [ 0 containers aborted ]
 [ 20 containers successful ]
 [ 0 containers failed ]
 [ 27 tests found ]
 [ 1 tests skipped ]
 [ 26 tests started ]
 [ 0 tests aborted ]
 [ 26 tests successful ]
 [ 0 tests failed ]

I agree that I have 27 tests with 1 skipped. But what’s this 20 containers? As you can see from the tree, we have:

  • 1 – JUnit Vintage – to run the JUnit 3/4 tests
  • 1 – JUnit Jupiter – to run the JUnit 5 tests (I didn’t have any in the project at the time I captured this tree so it is an empty container; but still counts)
  • 9 – JUnit test classes
  • 9 – The nine data elements in my parameterized test.

├─ JUnit Vintage ✔
│ ├─ com.devnexus.workshop.junit5.CentennialOlympicParkTest ✔
│ │ ├─ oldestForFirstElement ✔
│ │ ├─ oldestForLastElement ✔
│ │ ├─ oldestForEmptyList ✔
│ │ └─ url ✔
│ ├─ com.devnexus.workshop.junit5.EarthquakeTimeoutTest ✔
│ │ └─ timeout ✔
│ ├─ com.devnexus.workshop.junit5.ParkBuildingTest ✔
│ │ ├─ equalsValues ✔
│ │ └─ hashCodeValues ✔
│ ├─ com.devnexus.workshop.junit5.EarthquakeTest ✔
│ │ ├─ usingStandalone ✔
│ │ ├─ noMessageChecking ✔
│ │ └─ usingRule ✔
│ ├─ com.devnexus.workshop.junit5.GeorgiaAquariumTest ✔
│ │ ├─ namesOfShows ✔
│ │ └─ numberLines ✔
│ ├─ com.devnexus.workshop.junit5.OlympicsDatesTest ✔
│ │ ├─ [1996-07-19] ✔
│ │ │ └─ date[1996-07-19] ✔
│ │ ├─ [1996-07-20] ✔
│ │ │ └─ date[1996-07-20] ✔
│ │ ├─ [1996-07-31] ✔
│ │ │ └─ date[1996-07-31] ✔
│ │ ├─ [1996-08-03] ✔
│ │ │ └─ date[1996-08-03] ✔
│ │ ├─ [1996-08-04] ✔
│ │ │ └─ date[1996-08-04] ✔
│ │ ├─ [1996-07-18] ✔
│ │ │ └─ date[1996-07-18] ✔
│ │ ├─ [1996-08-05] ✔
│ │ │ └─ date[1996-08-05] ✔
│ │ ├─ [1997-07-18] ✔
│ │ │ └─ date[1997-07-18] ✔
│ │ └─ [1995-08-01] ✔
│ │ └─ date[1995-08-01] ✔
│ ├─ com.devnexus.workshop.junit5.CentennialOlympicParkEnumTest ✔
│ │ └─ allClosingTimesAfter9pm ✔
│ ├─ com.devnexus.workshop.junit5.ShakeExceptionTest ✔
│ │ └─ message ↷ this test is ignored becuase it is unnecessary
│ └─ com.devnexus.workshop.junit5.GeorgiaAquariumWaitTest ✔
│ ├─ plentyOfTime ✔
│ ├─ exactOpening ✔
│ ├─ anyMinuteNow ✔
│ └─ alreadyOpen ✔
└─ JUnit Jupiter ✔

Test run finished after 2518 ms
[ 20 containers found ]
[ 0 containers skipped ]
[ 20 containers started ]
[ 0 containers aborted ]
[ 20 containers successful ]
[ 0 containers failed ]
[ 27 tests found ]
[ 1 tests skipped ]
[ 26 tests started ]
[ 0 tests aborted ]
[ 26 tests successful ]
[ 0 tests failed ]