Jeroen De Dauw
Jeroen De Dauw

Reputation: 10908

Writing Java tests with data providers

I'm currently doing my first Java project and like to fully TDD it. I'm using JUnit for writing the tests. Apparently JUnit does not provide support for data providers, which makes it rather annoying to test the same method with 20 different versions of an argument. What is the most popular/standard testing tool for Java that does support data providers? I came across TestNG, but have no idea how popular that one is, or how it compares to alternatives.

If there is a way to get this behaviour is a nice way using JUnit, then that might also work.

Upvotes: 43

Views: 81853

Answers (6)

Alexander Kashpirovsky
Alexander Kashpirovsky

Reputation: 130

You can use https://genthz.org/ framework for test data generation.

Sample:

public class User {
    private Person person;

    private String login;

    private String password;
}

public class Person {
    protected String name;

    protected String lastName;

    protected Date birthday;

    private IdCard idCard;
}

User value = new DashaDsl() {
        {
//          Generate value for name fiedl of Persone class.
            path("person/name")
//              Set custom instance builder for field name of Person class.
                .simple(ctx -> "Alex");
        }
//      Use defaults configuration such as int, java.lang.Stirng and etc.
        }.def()
//      Get ObjectFactory
        .objectFactory()
//      Generate Person class object
        .get(User.class);

Upvotes: 0

ErikE
ErikE

Reputation: 50231

Here is another option. You don't have to use Google Guava, that is just my implementation.

This uses the same @Parameters as @dkatzel's answer, but instead of the class taking the arguments, the @Parameters annotation goes on specific test methods, so you can pick and choose which methods use that set of arguments.

import java.util.Collection;

import com.google.common.collect.ImmutableList;

import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(JUnitParamsRunner.class)
public class FrobTester {
    @SuppressWarnings("unused")
    private Collection validfrobAndGorpValues() {
        return ImmutableList.of(
            new Object[] {"frob28953", 28953},
            new Object[] {"oldfrob-189-255", 1890255}
        );
    }

    @Test
    @Parameters(method = "validfrobAndGorpValues")
    public void whenGivenFrobString_thenGorpIsCorrect(
        String frobString,
        int expectedGorpValue
    ) {
        // Arrange
        Frob frob = new Frob(frobString);

        // Act
        var actualGorpValue = frob.getGorpValue();

        // Assert
        Assert.assertEquals(actualGorpValue, expectedGorpValue);
    }
}

Upvotes: 4

JustAC0der
JustAC0der

Reputation: 3149

You can use JUnit 5's ParameterizedTest. Here's an example from https://www.petrikainulainen.net/programming/testing/junit-5-tutorial-writing-parameterized-tests/ :

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
 
import java.util.stream.Stream;
 
import static org.junit.jupiter.api.Assertions.assertEquals;
 
@DisplayName("Should pass the method parameters provided by the sumProvider() method")
class MethodSourceExampleTest {
 
    @DisplayName("Should calculate the correct sum")
    @ParameterizedTest(name = "{index} => a={0}, b={1}, sum={2}")
    @MethodSource("sumProvider")
    void sum(int a, int b, int sum) {
        assertEquals(sum, a + b);
    }
 
    private static Stream<Arguments> sumProvider() {
        return Stream.of(
                Arguments.of(1, 1, 2),
                Arguments.of(2, 3, 5)
        );
    }
}

It's possible to load test parameters from an annotation, a method or even a CSV file.

Upvotes: 9

Ingo B&#252;rk
Ingo B&#252;rk

Reputation: 20043

Coworkers of mine at our company wrote a freely available DataProvider in TestNG style for JUnit which you can find on github (https://github.com/TNG/junit-dataprovider).

We use it in very large projects and it works just fine for us. It has some advantages over JUnit's Parameterized as it will reduce the overhead of separate classes and you can execute single tests as well.

An example looks something like this

@DataProvider
public static Object[][] provideStringAndExpectedLength() {
    return new Object[][] {
        { "Hello World", 11 },
        { "Foo", 3 }
    };
}

@Test
@UseDataProvider( "provideStringAndExpectedLength" )
public void testCalculateLength( String input, int expectedLength ) {
    assertThat( calculateLength( input ) ).isEqualTo( expectedLength );
}

Edit: Since v1.7, it also supports other ways to provide data (strings, lists) and can inline the provider so that a separate method is not necessarily needed.

A full, working example can be found on the manual page on github. It also has a few more features, like collecting the providers in utility classes and accessing them from other classes etc. The manual page is very detailed, I'm sure you'll find any questions answered there.

Upvotes: 56

piotrek
piotrek

Reputation: 14540

Depending on your needs in flexibility vs readability, you can choose Parameterized - junit's built in option, described by dkatzel. Other options are external junit runners provided by external libraries like zohhak, which let's you do:

 @TestWith({
        "clerk,      45'000 USD, GOLD",
        "supervisor, 60'000 GBP, PLATINUM"
    })
    public void canAcceptDebit(Employee employee, Money money, ClientType clientType) {
        assertTrue(   employee.canAcceptDebit(money, clientType)   );
    }

or junitParams with a bit different functionality. just pick whatever suits you the most

Upvotes: 10

dkatzel
dkatzel

Reputation: 31648

JUnit 4 has parameterized test which is the does the same thing as php data providers

@RunWith(Parameterized.class)
public class MyTest{ 
     @Parameters
    public static Collection<Object[]> data() {
           /*create and return a Collection
             of Objects arrays here. 
             Each element in each array is 
             a parameter to your constructor.
            */

    }

    private int a,b,c;


    public MyTest(int a, int b, int c) {
            this.a= a;
            this.b = b;
            this.c = c;
    }

    @Test
    public void test() {
          //do your test with a,b
    }

    @Test
    public void testC(){
        //you can have multiple tests 
        //which all will run

        //...test c
    }
}

Upvotes: 41

Related Questions