Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mockito style anyXXX methods for unit testing

While unit testing some methods, there can be some scenarios where value of some parameters do not matter and can be any value.

For example in this piece of code:

public void method(String arg1, String arg2, int arg3){
    if(arg1 == null) throw new NullPointerException("arg1 is null");

    //some other code
}

unit testing the behavior that when arg1 is null then NPE must be thrown, the values of other arguments do not matter, they can be any value or be null.

So I wanted to document the fact that the values do not matter for the method under test.

I thought of following options:

Option 1: Define constants of ANY_XXX

I thought of explicitly creating constants ANY_STRING and ANY_INT, which contain a fixed value which documents that it can be any value and the method under test does not care about the actual value.

I can put all these constants in a single class called Any and reuse them across all test classes.

Option 2: Random values for ANY_XXX

This option seems a bit hacky to me as I have read somewhere that randomness should not be brought into test cases. But in this scenario this randomness will not be visible as the parameters will not create any side effect.

Which approach would be more suitable for better, readable tests?

UPDATE:

While I can use ANY_XXX approach by defining constants in Any class, but I am also thinking of generating ANY_XXX values with some constraints such as

Any.anyInteger().nonnegative();
Any.anyInteger().negative();

Any.anyString().thatStartsWith("ab");

I am thinking that maybe Hamcrest Matchers can be used for creating this chaining. But I am not sure if this approach is a good one. Similar methods for anyObject() are already provided by Mockito but those only work on Mocks and spies and not on normal objects. I want to achieve the same for normal objects for more readable tests.

Why I want to do this?

Suppose I have a class

class MyObject{

     public MyObject(int param1, Object param2){
          if(param1 < 0) throw new IllegalArgumentException();
          if(param2 == null) throw new NullPointerException();
     }
}

And now while writing tests for constructor

class MyObjectTest{

     @Test(expected=NullPointerException.class)
     public void testConstructor_ShouldThrowNullpointer_IfSecondParamIsNull(){
          
          //emphasizing the fact that value of first parameter has no relationship with result, for better test readability
          new MyObject(Any.anyInteger().nonnegative(), null);
     }
}
like image 800
Narendra Pathai Avatar asked Jan 07 '14 07:01

Narendra Pathai


Video Answer


2 Answers

I see both og them quite a lot

Personally I disagree that randomness should not be brought into tests. Using randomness to some degree should make your tests more robust, but not necessarily easier to read

If you go for the first approach I would not create a constants class, but rather pass the values (or nulls) directly, since then you see what you pass in without the need to have a look in another class - which should make your tests more readable. You can also easily modify your tests later if you need the other parameters later on

like image 170
Einar Bjerve Avatar answered Oct 05 '22 12:10

Einar Bjerve


My preference is to build up a utility class of constants along with methods to help with the creation of the constant values for tests, e.g.:

public final class Values {
    public static final int ANY_INT = randomInt(Integer.MIN_VALUE, Integer.MAX_VALUE);
    public static final int ANY_POSITIVE_INT = randomInt(0, Integer.MAX_VALUE);
    public static final String ANY_ISBN = randomIsbn();
    // etc...

    public static int randomInt(int min, int max) { /* omitted */ }
    public static String randomIsbn() { /* omitted */ }

    // etc...
}

Then I would use static imports to pull the constants and methods I needed for a particular test class.

I use the ANY_ constants only in situations where I do not care about the value, I find that they can make the intent of the test clearer, for example:

// when
service.fooBar(ANY_INT, ANY_INT, ANY_INT, ANY_INT, 5);

It's clear that the value 5 is of some significance - although it would be better as a local variable.

The utility methods can be used for adhoc generation of values when setting up tests, e.g.:

// given
final String isbn1 = randomIsbn();
final String isbn2 = randomIsbn();
final Book[] books = { new Book(isbn1), new Book(isbn2) };

// when
bookRepository.store(books);

Again, this can help to keep the test classes concerned about the tests themselves and less about data set up.

In addition to this I have also used a similar approach from domain objects. When you combine the two approaches it can be quite powerful. e.g.:

public final class Domain {
    public static Book book() {
        return new Book(randomIsbn());
    }
    // etc...
}
like image 36
Jonathan Avatar answered Oct 05 '22 11:10

Jonathan