Posts Tagged JUnit

NullPointerException on my test’s first use of a mock

This had happened before, but it got me again the other day: I was using Unitils‘ mock annotations (great stuff!) to set up my mocks, but my test failed with a NullPointerException the first place I tried to set an expection on the mock.  The reason being… I forgot to @RunWith(UnitilsJUnit4TestClassRunner.class) .  Sadly, I don’t think I was much faster diagnosing the problem this time than I was last time.

Goal: Next time this symptom appears, instantly suspect that I’m using the wrong runner!  :)

Advertisements

, , , ,

Leave a comment

The Little Integration Test that Didn’t, part 2

In part 1, we walked through how we decided to add the rollback-for=”Exception” attribute to our transactional advice.  What we didn’t discuss is how these changes dovetailed with changes to the existing integration test.

The Integration Test, in its Natural Habitat

The integration test consists of three modules, simulating the layers in our system.  There is a persistence project, a domain project, and a service project.  The service-level project has classes named such that the transactional AOP advice applies to their methods.

So far, so good, right?

The Existing Rollback Test

The service-level project contained an integration test, a junit class with testSuccessfulCommit() and testRollback() methods.  The testRollback() method passed a special string such that, when the call got down to the persistence layer, the persistence layer would recognize it and throw a RuntimeException.  Then the integration test would catch that and verify that the data could not be found in the database — the idea being that if the thing we just wrote to the database couldn’t be found, the rollback must have succeeded.

Testing Rollback on Exceptions and Errors

When we changed the transaction advice to also roll back on Exception, we weren’t sure if that would remove the default of rolling back on RuntimeException and Error.  So we modified the DAO at the persistence layer, which could already throw a RuntimeException, to recognize requests for Exception and Error as well.  Then we modified the service-layer integration test to test the transactional behavior for those kinds of exceptions (with a bunch of Maven installs and (possibly unnecessary) Maven Eclipses being run between all these changes).

Two Problems

When we changed the transactional advice to include Exception, we were getting really odd failures in the service-level integration test.  These errors were caused by two problems in the integration test that banded together against us:

1. An Error of Commission: we never tried to write!

Here was what we’d updated the create() method to look like:


    public Long create(SimplePerson persistentObject) throws MyException {
        // Simulate something going wrong at the persistence layer
        if (persistentObject.getSsn().equals("Exception")) {
            throw new MyException();
        } else if (persistentObject.getSsn().equals("RuntimeException")) {
            throw new MyRuntimeException();
        } else if (persistentObject.getSsn().equals("Error")) {
            throw new MyError();
        }

        Session session = sessionFactory.getCurrentSession();
        return (Long) session.save(persistentObject);
    }

Do you see the problem?

If we passed the special SSN to have create() throw an exception, it would never even try to save the persistent object to disk.  So when (back at the service layer) we told it to throw an Exception, a rollback appeared to have occurred — leastways, the Person Id couldn’t be found in the database.

The persistence layer needed to really save the object:


    public Long create(SimplePerson persistentObject) throws MyException {
        Session session = sessionFactory.getCurrentSession();
        Long id = (Long) session.save(persistentObject);
        session.flush();

        // Simulate something going wrong at the persistence layer
        if (persistentObject.getSsn().equals("Exception")) {
            throw new MyException();
        } else if (persistentObject.getSsn().equals("RuntimeException")) {
            throw new MyRuntimeException();
        } else if (persistentObject.getSsn().equals("Error")) {
            throw new MyError();
        }

        return id;
    }

Now (after Maven installing this, I mean) our test would show the true state of things — once we cleaned up our dirty data!

2. Dirty Data

Yes, we’d unwittingly gotten into a state where data leftover from a failed test caused future runs of the test to fail. What would happen is that if we expected a test to throw an exception but it didn’t (due in our case  to me accidentally not passing one of the words the persistence layer was watching for in the SSN field), a record would be written and never cleaned up.

Complication 1: Not Realizing Which Assert Failed

I didn’t realize the dirty data problem for a while (resulting in a few extra rounds of changing the AOP advice beans in that project, Maven Installing that project, and re-running the tests at the integration test service layer), because when the test resulted in an AssertionError involving Assert.assertNull, I assumed it was the final assertNull at the end of the test, the one that tests whether the record finally ended up on disk or not.

Here’s what one of the test methods looked like:


    @Test
    public void testRuntimeExceptionRollback() throws MyException {
        final String ssn = "RuntimeException";
        Assert.assertNull(personSvc.findBySsn(ssn));
        SimplePerson person = new SimplePerson();

        //...
        try {
            personSvc.savePerson(person);
            Assert.fail("Should have gone boom");
        } catch (MyRuntimeException success) {
            SimplePerson person2 = personSvc.findBySsn(ssn);
            Assert.assertNull(person2);
        }
    }

But actually the assert that was failing was the one on line 4 where it checks for dirty data by asserting that the record should not already exist.  (Should be using the Assume class, which New Ben just told me about a day or few ago, for this type of pre-checking?)

Solution to Complication 1

The solution to this confusion was to put some text on these asserts so they could be easily differentiated:


        Assert.assertNull("Should start out null", personSvc.findBySsn(ssn));
        //...
            Assert.assertNull("Should still be null due to rollback", person2);

Complication 2: No Clean Way to Clean Up

We wanted to clean up all the records in the @Before and @After method, but you had to know the object Id in order to delete it.  With our existing DAO interface, we would have needed to loop calling findBySsn()… it was easier and cleaner to just add a deleteAll() method to the DAO, and (after exposing it through the domain layer project to the service layer project) then call that from our service layer test’s @Before and @After method.

We were finally in a position to test out our changes to the transactional advice.

, , ,

Leave a comment

JUnit: Dealing with errors in your @After method

Normally, when you run a junit test and it has an error, you look at the error and it helps you figure out what’s wrong.  But I encountered a situation today that was a layer deeper:

Here’s what we saw:

A NullPointerException in the tearDown method (technically, in the @After method since we’re using JUnit 4).  The error didn’t give much clue as to what the cause was.

What happens in cases like this is that when the @After method causes an exception, that exception masks any previous exception that the @Before method or @Test method may have experienced.

To diagnose the problem, we comment out the contents of the @After method for the moment and then run the test again (maybe just running one test method (you can do this by opening the class in Eclipse’s  Outline view, then right-clicking on one of the methods and Run As… JUnit Test) to avoid accumulating a bunch of gunk that the @After method would have cleaned up if it weren’t commented out).  Now that we had removed the masking exception, the stack trace showed clearly what the problem was (in our case, there were some properties that needed to be set in a properties file).

Leave a comment

Running with the…

(For some reason, “Running with the bulls” sounds a lot more poetic than “Running with the right JUnit 4 test runner.”)

I was getting a NullPointerException when I started writing the unit test for my little TransactionManager wrapper class.  I am using Unitils to manage all the dependency injection, so my test class looks something like this:


public class TransactionManagerTest {
    @Mock
    @InjectIntoByType
    private PlatformTransactionManager platformTransactionManager;

    @TestedObject
    private TransactionManager transactionManager;

    @Test
    public void testCommitException() {
        transactionManager.commit();
    }

I first got the NullPointerException while trying to test TransactionManager’s getTransaction() method, but it has some more complex logic, so I decided to back off from that and test the commit() method first.

I had expected that when I called transactionManager.commit(), I would get the unchecked exception that commit() throws when you haven’t called getTransaction() first.  But instead, this NullPointerException.

Before looking at the answer, can you guess why that would be?

Oh, wait, I already gave it away at the top of this post. Rats!

(Maybe there’s a reason I’m a programmer instead of a game show host. ;)

Yup, that’s right, folks!  All it was, was, I needed to…


@RunWith(UnitilsJUnit4TestClassRunner.class)

See ya next time!

, , ,

Leave a comment

Writing a parameterized JUnit test

JUnit 4 supports parameterized tests.  There are a few things that confuse me about how you set up your test class for it, though.  Let’s see what it would look like if what we wanted was to run the same test class, using a different Map<String,String> of settings each time.

The Normal Things

There are some elements of setup for using the parameterized test that didn’t confuse me.  Let’s briefly list them:

  • The test class needs to be decorated with the @RunWith(Parameterized.class) annotation
  • You need a public static data-providing method, decorated with the @Parameters annotation
  • Each test method is decorated with @Test (as usual)

Things I Found Confusing

The Data-Providing Method’s Return Type

The data-providing @Parameters method has to return a Collection<> of arrays.  Now, ever since at least Principles of Programming I & II in college, I’ve had trouble remembering which subscript is which when you have a multidimensional array.  So when I saw this helpful example, my brain got stuck on line 1 of what the @Parameters method was returning:

  return Arrays.asList(new Object[][] {
   {"22101", true },
   {"221x1", false },
   {"22101-5150", true },
   {"221015150", false }});

I couldn’t think how this would translate to my maps I wanted to run with.

The Special Constructor

When you’re using the JUnit 4 parameterized test, your test class needs to have a constructor that takes one set of the parameters and stores them to fields in the test class for use during that run.  But I couldn’t figure out — should my constructor expect a Map<String, String>[]?  A Map<String, String>?

Figuring it Out

There were a few different facets that it helped me understand:

Each Element of the Collection is a set of Parameters

The reason the @Parameters method must return a collection of Arrays is because each Array holds the parameters that are needed for one test scenario.  So if my test class needed a Map, an int, and a boolean, the @Parameters method would return a collection of three-element arrays — each array containing the parameters for one configuration of the test class.  This leads to the next facet…

The Test Class Constructor Should Accept One Parameter for Each Element of the Array

The JUnit parameterized test mechanism expects to instantiate the test class by calling a constructor that has the same number of arguments as there are elements in the current parameters array*.  If my test class FooTest needed a Map, an int, and a boolean each time, the constructor might look something like this:

    public FooTest(Map<String, String> map, int value, boolean flag) {
        //...
    }

…and my @Parameters method would need to return a Collection of Arrays of Object (“of Object” since for a given array, the three elements would be of different types).

*I haven’t read or tested to see if the JUnit mechanism supports a collection of jagged arrays of configuration parameters such that (for instance) sometimes the test class might be instantiated using the two-arg constructor, other times using its three-arg constructor…

Store the Parameters in Private Fields in your Test Class

What you’d normally do is store the parameters you get constructed with to private fields, for use by the @Test methods:

@RunWith(Parameterized.class)
class FooTest {
    private Map<String, String> map;
    private int value;
    private boolean flag;

    public FooTest(Map<String, String> map, int value, boolean flag) {
        this.map = map;
        this.value = value;
        this.flag = flag;
    }

    //...
}

The Application to My Case

I think I was more confused because I only needed one parameter — a Map<String, String> — so it wasn’t apparent to me why the Collection of Object Arrays was needed.

So the “hard parts” of my test class end up looking something like this:


@RunWith(Parameterized.class)

class FooTest {

    private Map<String, String> map;

    @Parameters
    public static Collection<Object&#91;&#93;> configs() {
        Map<String, String> map1 = new HashMap<String, String>();
        map1.put("Name", "Bill");
        map1.put("Favourite Color", "Blue");

        Map<String, String> map2 = new HashMap<String, String>();
        map2.put("Name", "Sam");
        map2.put("Favourite Color", "Plaid");

        return Arrays.asList(new Object[][] {
                { map1 },
                { map2 }
        });

    public FooTest(Map<String, String> map) {
        this.map = map;
    }

    //...
}

(Maybe nobody else needed that explanation, but it helped me!  :)

,

22 Comments