user1569891
user1569891

Reputation: 46

JUnit Testing Constructor Testing

Guys I am new to JUnit testing and trying to get a good grip on it, right now I am writing JUnit tests for a constructor (for Digraph Class that creates a directed graph) that throws IllegalArgumentException when it reads negative int value and creates a graph if everything is ok (number of node value) is greater than zero.

Digraph Class:

 In in = new In();
 public Digraph(In in) {
  try {
    this.nodes = in.readInt();
    System.out.println("Total nodes in graph: "+ nodes);
    if (nodes < 0) throw new IllegalArgumentException("Number of vertices must be > 0);
    int E = in.readInt();
    if (E < 0) throw new IllegalArgumentException("Number of edges must be >0);
  }catch (NoSuchElementException e) {
     throw new InputMismatchException("Invalid input format in Digraph constructor");
  }

Below is the test that I am trying to write:

@Rule
  public ExpectedException exception = ExpectedException.none();  

@Test(expected = IllegalArgumentException.class)
public void DigraphIn() {

    Digraph G = new Digraph(in.readInt());

    exception.expect(IllegalArgumentException.class);
    exception.expectMessage("Vertices can't be nagative");
    exception.expectMessage("Invalid input format in Digraph constructor");
    exception.expectMessage("Number of edges in a Digraph must be nonnegative");
try{
}catch (AssertionError e){
    }
}

How should I test both of the cases using one (or two) test cases? If there's no -ve value detected by "in" I get java.lang.AssertionError otherwise test passes. Thanks in advance

Upvotes: 0

Views: 13174

Answers (2)

Tobb
Tobb

Reputation: 12225

When you are testing a method you actually call it, which will make it execute. The test can only verify one exception per test, since the exception will cancel the remainder of the processing of the method. Thus, you need one test for each of the places where an exception can be thrown.

When verifying that an exception is thrown with a unit-test, there are basically 3 ways to do it:

A try-catch block:

@Test
public void myTest() {
    try {
        myClass.myMethod(42);
        fail();
    } catch(final IllegalArgumentException e) {
        assertEquals("something went wrong", e.getMessage());
    }
}

The expected-attribute of the @Test-annotation:

@Test(expected=IllegalArgumentException.class)
public void myTest() {
    myClass.myMethod(42);
}

and ExpectedException:

@Rule
public ExpectedException expectedException = ExpectedException.none();

@Test
public void myTest() {
    expectedException.expect(IllegalArgument.class);
    expectedException.expectMessage("something went wrong");

    myClass.myMethod(42);
}

In your example, your trying to use all three.

If we compare the methods of testing for exceptions, only the first and third are actually able to make verifications on the exception being thrown, which makes them preferable for testing methods where the same type of exception can be thrown from multiple places, you can then use the exception message to verify that the exception was thrown from the place you ment it to.

The second one is by far the most readable, but does not allow you to distinguish between the exceptions being thrown by the method under test, it does not provide as much value as the other two in most cases.

Of the first and the third, the third is by far the most readable, and also my personal favorite.

Since the method under test can only throw one exception per execution, it should have one test method for each of the places where an exception may be thrown:

public class DiagraphTest {

    @Rule
    public ExpectedException expectedException = ExpectedException.none();

    private Diagraph diagraph;
    private In in;

    @Before
    public void setup() {
        in = mock(In.class); 
    }

    @Test
    public void constructorShouldThrowExceptionWhenNumberOfVerticesIsLessThanOne() {
         expectedException.expect(IllegalArgumentException.class);
         expectedException.expectMessage("vertices must be > 0"); //expectMessage only needs a substring of the exception-message

         doReturn(-1).when(in).readInt();

         new Diagraph(in);
    }

    @Test
    public void constructorShouldThrowExceptionWhenNumberOfEdgesIsLessThanOne() {
         expectedException.expect(IllegalArgumentException.class);
         expectedException.expectMessage("edges must be > 0"); 

         when(in.readInt()).thenReturn(42, -1);

         new Diagraph(in);
    }

    //as to the last exception, I really can't see that it will ever be thrown in that try-block, but here's a test for that as well..
    @Test
    public void constructorShouldThrowInputMismatchExceptionIfReceivedNoSuchElementException() {
         expectedException.expect(InputMismatchException.class);
         expectedException.expectMessage("Invalid input format); 

         doThrow(new NoSuchElementException("phail")).when(in).readInt();

         new Diagraph(in);
    }

}

Upvotes: 1

Claudiu
Claudiu

Reputation: 1489

You shoud have many testcases. It is good to have one for each exception.

Each test you perform is different and should be treated differently.

A good reference is this: Junit Cookbook.

Actually I saw an error in your code. In your test cases you may mock the collaborators as below. I made a mock that returns at each call different values using 'mockito' mocks library.

You basically need something like:

@Test(expected = IllegalArgumentException.class)
public void DigraphInThatThrowsExceptionForVertices() {
    In in = Mockito.mock(In.class);
    when(in.readInt()).thenReturn(-1);
    Digraph G = new Digraph(in);
    fail();
}

@Test(expected = IllegalArgumentException.class)
public void DigraphInThatThrowsExceptionForEdges() {
    In in = Mockito.mock(In.class);
    when(in.readInt()).thenReturn(10).thenReturn(-1);
    Digraph G = new Digraph(in);
    fail();
}

@Test
public void DigraphInThatDoesNotThrowException() {
    In in = Mockito.mock(In.class);
    when(in.readInt()).thenReturn(10).thenReturn(15);
    Digraph G = new Digraph(in.readInt());
}

This way test code is cleaner and easy to read.

Upvotes: 2

Related Questions