Introduction to JUnit Theories

A normal test captures the intended behavior in one particular scenario, given an input it expects a certain output. A theory captures some aspect of the intended behavior in possibly infinite numbers of potential scenarios. This means whatever a theory asserts is expected to be true for all data sets. Theories are often used for finding bugs in boundary-value cases or mathematical theories. Theories are functionally similar to parameterized tests, but are expressively richer.

Creating a Theory

The class should be annotated with @RunWith(Theories.class) and have:

  1. A data method that generates and returns test data
    • By annotating a static member variable with @DataPoint
    • By annotating a static member variable with @DataPoints
  2. A theory by annotating a test method with the @Theory annotation

Theories come up with many annotations and a class runner. Let’s examine the important annotations and classes in theory:

  • @Theory same like @Test, this annotation identifies a theory test.
  • @DataPoint annotation identifies a single set of test data. This annotation is similar to @Parameter. It can be annotated by either a static variable or a method.
  • @DataPoints annotation identifies multiple sets of test data. This annotation is similar to @Parameters and is generally used for an array. It can be annotated by either a static variable or a method.
  • @ParametersSuppliedBy annotation provides the parameters to the test cases.
  • Theories is a JUnit runner for running theory test classes.
  • ParameterSupplier is able to provide parameters that we can supply to the test case.

Passing Data Via @DataPoint

In contrast to a normal test, theories can have arguments. The data that is passed to these arguments come from a static member variable annotated by either @DataPoint or @DataPoints. When multiple @DataPoint annotations are defined in a test, the theories apply to all possible type complient combinations of data points for the test arguments.

package com.memorynotfound.test;

import org.junit.experimental.theories.DataPoint;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;

@RunWith(Theories.class)
public class TestTheoriesDataPoint {

    @DataPoint
    public static String dolly = "dolly";
    @DataPoint
    public static String parton = "parton";

    @Theory
    public void test(String first, String last){
        System.out.println("testing first: " + first + " with last: " + last);
    }

}

The previous code generates the following output. 2 x 2 combinations equals to 4 executions.

testing first: dolly with last: dolly
testing first: dolly with last: parton
testing first: parton with last: dolly
testing first: parton with last: parton

Passing Data Via @DataPoints

The @DataPoints annotation is used to provide a set of data. It will execute the theory with 9 (3 ^ 2) possible combinations. The @DataPoints annotations can be used on static member variables or methods.

package com.memorynotfound.test;

import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;

import static org.junit.Assert.assertTrue;

@RunWith(Theories.class)
public class TestTheoriesDataPoints {

    @DataPoints
    public static int[] bounds = {Integer.MIN_VALUE, 0, Integer.MAX_VALUE};

    @Theory
    public void addition_is_commutative_test(int a, int b){
        System.out.println("a + b = " + (a + b) + " == b + a = " + (b + a));
        assertTrue(a + b == b + a);
    }

}

The previous code generates the following output.

a + b = 0 == b + a = 0
a + b = -2147483648 == b + a = -2147483648
a + b = -1 == b + a = -1
a + b = -2147483648 == b + a = -2147483648
a + b = 0 == b + a = 0
a + b = 2147483647 == b + a = 2147483647
a + b = -1 == b + a = -1
a + b = 2147483647 == b + a = 2147483647
a + b = -2 == b + a = -2

You may also like...