In Part 1 of this tutorial, we looked at performing parameterized tests in JUnit, and in Part 2 we looked at JUnit’s Rule support. In this portion, we will look at a lesser-used feature of JUnit – assumptions.

The ideal unit test runs anytime, anywhere and quickly. This is central to the whole Test Driven Development (TDD) approach – tests are to be run early (before you’ve even written the code) and often (every single time you change the code).

While, at least in my experience, this can be achieved the vast majority of the time, there are always those few outlying conditions. A few potential examples:

  • Tests may be dependent on resources that are not always present. As one example, a test that assumes that the computer on which it is running has an IP address may fail if run on a laptop in “airplane mode.”
  • Certain tests may only be appropriate when run in certain environments. It’s possible, for example, that your Killer App has to do something extra when running under Microsoft Windows that it doesn’t do when run under OS X. Or perhaps there’s Selenium-based test that should be run against Internet Explorer, but not other browsers, because your user interface is slightly different in IE. (Sorry, Microsoft – not trying to pick on you.)

These situations are generally pretty rare when you are in control of the source code being tested, but they can crop up a bit more frequently when you’re writing unit tests for legacy code that wasn’t designed with testing in mind.

One option, of course, is to just let the tests fail if it’s caused by something truly ephemeral. If you’re only banging away on your laptop on an airplane once a year or so, having the IP-address-related tests fail might not bother you. Tests that routinely generate false positives, however, undermine the whole strategy of unit testing, because they cause developers to become accustomed to test failures, making a failed test something to be ignored or “looked into next week,” rather than Something That Needs To be Dealt With Now.

A second option would be to go in and temporarily mark the tests with an @Ignore annotation. This will tell JUnit not to actually try to execute the test. @Ignore is unconditional, however. In our airplane case, we’d have to remember to go back and “un-ignore” the tests when we landed, or else we would have unnecessarily removed them from the test suite. This clearly won’t work in the “environmental” case.

Thus, what is really needed is a way to be able to say “under this particular set of circumstances, this test should not be run.”

Enter JUnit assumptions.

Just like the assert methods that we all know and love, JUnit 4 has an assumeTrue method. This is a static method of org.junit.Assume and takes a single boolean parameter. If you call assumeTrue with a boolean expression that evaluates to false, JUnit will abort the test at that point and proceed to the next test, without failing this test.

An example:


public class ObjectWithFailingMethodTest
{
	private ObjectWithFailingMethod objUnderTest;
	
	public ObjectWithFailingMethodTest()
	{
	}
	
	@Before
	public void setup()
	{
		objUnderTest = new ObjectWithFailingMethod();
	}
	
	@Test
	public void methodThatPasses_shouldReturnTrue()
	{
		assertTrue(objUnderTest.methodThatPasses());
	}
	
	@Test
	public void methodThatFails_shouldReturnTrue()
	{
		assertTrue(objUnderTest.methodThatFails());
	}
}

ObjectWithFailingMethod is simply a dummy class I’ve created. It’s intended to represent the situation where both of its methods are supposed to return true, however for reasons outside the test’s or code’s control methodThatFails is returning false instead. As a result, the second test (methodThatFails_shouldReturnTrue) fails when we run this test:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.silverbaytech.junitTricks.ObjectWithFailingMethodTest
Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.064 sec <<< FAILURE!

Results :

Failed tests: 
  methodThatFails_shouldReturnTrue(com.silverbaytech.junitTricks.ObjectWithFailingMethodTest)

Tests run: 2, Failures: 1, Errors: 0, Skipped: 0

Having confirmed that this failure is due to the peculiar environment in which I’m currently running, and not a systematic problem, I can arrange for JUnit to skip this test by making the following changes:

	private boolean shouldTestMethodThatFails()
	{
		return false;
	}
	
	@Test
	public void methodThatFails_shouldReturnTrue()
	{
		assumeTrue(shouldTestMethodThatFails());
		assertTrue(objUnderTest.methodThatFails());
	}

In a real test, of course, shouldTestMethodThatFails would contain code that would determine whether or not we are in an environment in which this test should be executed. The addition of the assumeTrue in the test (and the fact that it is passed a value of false) causes the test to be aborted at that point, with the result that our build is now happy:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.silverbaytech.junitTricks.ObjectWithFailingMethodTest
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.057 sec

Results :

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

Note that the test does not actually show up as “skipped.” JUnit records that the test was run (because it was begun), and that it did not fail (because it was short-circuited), so the output is as if it succeeded. “Skipped” tests are those annotated with @Ignore.

assumeTrue may also be used in methods annotated with @Before or @BeforeClass. If all the tests in a particular test class are short-circuited this way, JUnit typically behaves as if the entire class were not present (or didn’t contain any tests) – this class won’t be listed as having been run.

assumeTrue isn’t the only variant available on org.junit.Assume. In addition, there are the following methods:

  • assumeNotNull can be called with one or more objects, and will short circuit the test if any of the objects are null. Thus, if you’re expecting, say, a factory to return you something on which your test depends and the factory fails, you can use this to skip the remainder of this test.
  • assumeNoException can be used to short-circuit a test when an environmental condition might cause an unexpected exception:
    	@Test
    	public void parseDataFile()
    	{
    		DataFile file;
    		try
    		{
    			file = DataFile.open("sampledata.txt");
    		}
    		catch (IOException e)
    		{
    			// stop test and ignore if data can't be opened
    			assumeNoException(e);
    		}
    		// test the parsing logic
    	}
    

    (This is a somewhat contrived example from the JUnit documentation, but illustrates the basic idea. Normally, I would write tests so that they provided their own sample data so that they wouldn’t be dependent on external files.)

  • assumeThat takes a pair of arguments: something to check and a Hamcrest Matcher. Thus, you can use Hamcrest syntax for assumptions just the way that you can use it for assertions.

As I said before, it is usually possible to write your code and your tests so that you don’t need this particular construct, but in that 1-in-a-1,000 case, it’s nice to have the tools to get the job done properly.