0

JUnit 4.12

I'm currently writing a test for a class methods. Here is how it looks like

public interface MyInterface{
    //method declaration
}

public class MyClass implements MyInterface{

    private int a;
    private in b;

    public MyClass(int a, int b){
        if(a <= b + 5)
            throw new IllegalArgumentException("Invalid arguments");
        this.a = a;
        this.b = b;
    }

    //methods
}

Now I want to test this class:

public class MyClassTest{
    private static final int THRESHOLD = 1000;

    private MyClass mc;

    @Before
    public void init(){
        Random rnd = new Random();
        int a = rnd.nexInt(THRESHOLD), 
            b = rnd.nexInt(THRESHOLD);
        mc = new MyClass(a, b);
    }
}

But in this case, init() might throw an exception. So I'd like to test preserving invariants as well as initialize an object in order to test its other methods.

How to do this correctly in JUnit?

stella
  • 2,546
  • 2
  • 18
  • 33
  • 4
    This unit test is incorrectly designed. You normally would have 3 test cases with fixed values for `a` and `b`. One tests if the case where `a` lower than `b+5`, one tests where `a` is equal to `b+5` and the last one tests where `a` is larger than `b+5`. Using `Random` is not necessary or helpful here. – Tom May 22 '16 at 07:37
  • Why do this in `@before`? If you want to test the exception, just just a Test. – Rob Audenaerde May 22 '16 at 07:41
  • 1
    In most cases, it is a bad idea to use random/non-deterministic values when testing. Even in classes that are supposed to behave in some random fashion, tests are usually designed such that their behaviors become predictable somehow. – Psycho Punch May 22 '16 at 07:41
  • 5
    I think OP misunderstood the function of `@Before`. Normally, one uses `@Before` within a Test-class to initialize some values needed for the tests. This method does not call any tests, the JUni-Runner does that for you. – Turing85 May 22 '16 at 07:42
  • 2
    I agree with @Psycho Punch. What you want to do is test definite values that are known in order to control your tests. If you wish to test an exception, put a value in there that will throw an exception and then put @Test(expected = Exception.class) for that test. See the following link for a better understanding of how to test exception. http://stackoverflow.com/questions/15216438/junit-testing-exceptions – Dale May 22 '16 at 07:46

3 Answers3

0

@Before is designed to initialize anything that you need for the test. If you want to check anything - @Test methods are the obvious place.

In your case, I think you should create several separate test methods that pass valid and invalid parameters explicitly. This way if the test fails, you know why it happened and if it doesn't fail - you can be sure that the logic is correct. If you use random you can't be sure: maybe the tested code is still correct or random value was just suitable and you are lucky.

Art
  • 1,302
  • 13
  • 25
0

You need two test classes.

First one for testing constructor behaviour. Second for testing methods behaviour. Also, I will assume that every tested method needs the code defined in @before.

So we have:

public interface MyInterface{
    //method declaration
}

public class MyClass implements MyInterface{

private int a;
private in b;

public MyClass(int a, int b){
    if (a <= b + 5) {
        throw new IllegalArgumentException("Invalid arguments");
    }
    this.a = a;
    this.b = b;
}

//methods
}

Now we create test for constructor

public class MyClassConstructorTest{
private static final int THRESHOLD = 1000;

@Test
public void allArgsConstructor_okValues_shouldCreateObjectOK(){
    // Given
    int a = 0; 
    int b = a - 5;
    // tested method
    new MyClass(a, b);
    // then no exception (test: ok)
}


@Test(expected = IllegalArgumentException.class)
public void allArgsConstructor_aLesserBplus5_shouldThrowException(){
    // Given
    int a = 0; 
    int b = a; 

    // tested method
    new MyClass(a, b);
    // then fail constructor assertion
    // see expected annotation
}

// other tests for constructor you need?

}

After that you create your normal tests:

public class MyClassTest{
private static final int THRESHOLD = 1000;

private MyClass mc;

@Before
public void init(){
    int a = 0; 
    int b = a+5;
    mc = new MyClass(a, b);
}

// normal test that use @before
}

PS: also pointed in comments: never use random in tests.

PS2: not sure if you can but you probably should rename your question to: "how to test constructor method"

Andrii Plotnikov
  • 3,022
  • 4
  • 17
  • 37
0

I think your problem starts with the use of random numbers. If you use random numbers in a unit test you have to make sure that the range you are covering fit in one case and only one case. In your case you actually have three cases:

  1. a < b + 5 - Which generates your exception (btw, it's good practise to always use accolades : {}).
  2. a > b + 5 - Which is the valid case.
  3. a = b + 5 - Which is another invalid case

You need to test the three cases and so you need a setup for each case. The issue you are running to is that you are trying to combine all cases in one. This kind of testing is highly discouraged in unit tests because you are introducing a variable code component with that which as you say, needs to be tested. As a suggestion, make 3 b variables b1 and b2 and b3

Say a is a random number. Make b1 = a - 5 - 1 and b2 = a - 5 + 1 and b3 = a - 5. Then provide 3 test methods for each case. I advise you to use coverage tools like for example MoreUnit because it makes sure you detect all different cases that you actually need to test more easily.