InsertNickHere
InsertNickHere

Reputation: 3666

JUnit Exception Testing

Edit: Not JUnit 4 available at this time.

Hi there,

I have a question about "smart" exception testing with JUnit. At this time, I do it like this:

public void testGet() {

    SoundFileManager sfm = new SoundFileManager();

        // Test adding a sound file and then getting it by id and name.
        try {
            SoundFile addedFile = sfm.addSoundfile("E:\\Eclipse_Prj\\pSound\\data\\Adrenaline01.wav");
            SoundFile sf = sfm.getSoundfile(addedFile.getID());
            assertTrue(sf!=null);
            System.out.println(sf.toString());

            sf = sfm.getSoundfileByName("E:\\Eclipse_Prj\\pSound\\data\\Adrenaline01.wav");
            assertTrue(sf!=null);
            System.out.println(sf.toString());
        } catch (RapsManagerException e) {
            System.out.println(e.getMessage());
        }

        // Test get with invalid id. 
        try {
            sfm.getSoundfile(-100);
            fail("Should have raised a RapsManagerException");
        } catch (RapsManagerException e) {
            System.out.println(e.getMessage());
        }

        // Test get by name with invalid name
        try {
            sfm.getSoundfileByName(new String());
            fail("Should have raised a RapsManagerException");
        } catch (RapsManagerException e) {
            System.out.println(e.getMessage());
        }

    }

As you can see, I need one try/catch block for each function that is supposed to throw an exception. It seems not to be a good way to do this - or is there no possibility to reduce the use of try/catch?

Upvotes: 15

Views: 53775

Answers (5)

Jordan
Jordan

Reputation: 1619

In Java 8, you can use lambda expressions to get tighter control over when the exception is thrown. If you use the annotations method then you're only asserting that the exception is thrown somewhere in the test method. If you're executing more than one line of code in the test then you risk your test passing when it should fail. Java 8 solution is something like this.

static void <T extends Exception> expectException(Class<T> type, Runnable runnable) {
    try {
        runnable.run()
    } catch (Exception ex) {
        assertTrue(ex.getClass().equals(type));
        return;
    }
    assertTrue(false);
}

Usage:

@Test
public void test() 
    MyClass foo = new MyClass();
    // other setup code here ....
    expectException(MyException.class, () -> foo.bar());
}

Upvotes: 1

rwitzel
rwitzel

Reputation: 1818

The most concise syntax is provided by catch-exception:

public void testGet() {
    SoundFileManager sfm = new SoundFileManager();
    ... // setup sound file manager

    verifyException(sfm, RapsManagerException.class)
       .getSoundfile(-100);

    verifyException(sfm, RapsManagerException.class)
       .getSoundfileByName(new String());
}

Upvotes: 2

skaffman
skaffman

Reputation: 403441

I suggest that you need to break up testGet into multiple separate tests. The individual try/catch blocks seem to be pretty independent of each other. You may also want to extract the common initialization logic into its own setup method.

Once you have that, you can use JUnit4's exception annotation support, something like this:

public class MyTest {

private SoundManager sfm;

@Before
public void setup() {
      sfm = new SoundFileManager();
}

@Test
public void getByIdAndName() {
  // Test adding a sound file and then getting it by id and name.
      SoundFile addedFile =              
      sfm.addSoundfile("E:\\Eclipse_Prj\\pSound\\data\\Adrenaline01.wav");
      SoundFile sf = sfm.getSoundfile(addedFile.getID());
      assertTrue(sf!=null);
      System.out.println(sf.toString());

      sf = sfm.getSoundfileByName("E:\\Eclipse_Prj\\pSound\\data\\Adrenaline01.wav");
      assertTrue(sf!=null);
      System.out.println(sf.toString());
}

@Test(expected=RapsManagerException.class)
public void getByInvalidId() {
      // Test get with invalid id. 
      sfm.getSoundfile(-100);
}

@Test(expected=RapsManagerException.class)
public void getByInvalidName() {
      // Test get with invalid id. 
      sfm.getSoundfileByName(new String());
}
}

Upvotes: 33

P&#233;ter T&#246;r&#246;k
P&#233;ter T&#246;r&#246;k

Reputation: 116246

With JUnit 4, you can use annotations instead. However, you should separate your test into 3 distinct methods for this to work cleanly. Note that IMHO catching an exception in the first scenario should be a failure, so I modified the catch block accordingly.

public void testGet() {
    SoundFileManager sfm = new SoundFileManager();

    // Test adding a sound file and then getting it by id and name.
    try {
        SoundFile addedFile = sfm.addSoundfile("E:\\Eclipse_Prj\\pSound\\data\\Adrenaline01.wav");
        SoundFile sf = sfm.getSoundfile(addedFile.getID());
        assertTrue(sf!=null);
        System.out.println(sf.toString());

        sf = sfm.getSoundfileByName("E:\\Eclipse_Prj\\pSound\\data\\Adrenaline01.wav");
        assertTrue(sf!=null);
        System.out.println(sf.toString());
    } catch (RapsManagerException e) {
        fail(e.getMessage());
    }
}

@Test(expected=RapsManagerException.class)
public void testGetWithInvalidId() {
    SoundFileManager sfm = new SoundFileManager();

    sfm.getSoundfile(-100);
}

@Test(expected=RapsManagerException.class)
public void testGetWithInvalidName() {
    SoundFileManager sfm = new SoundFileManager();

    sfm.getSoundfileByName(new String());
}

Upvotes: 3

Donal Fellows
Donal Fellows

Reputation: 137557

If you have an expected exception and you can't use an annotation to trap it, you need to catch it and assert that you've got what you expected. For example:

Throwable caught = null;
try {
   somethingThatThrows();
} catch (Throwable t) {
   caught = t;
}
assertNotNull(caught);
assertSame(FooException.class, caught.getClass());

If you can use an annotation instead, do that as it's much clearer. But that's not always possible (e.g., because you're testing a sequence of methods or because you're using JUnit 3).

Upvotes: 13

Related Questions