Junit Parameterized Tests – @Theory And @DataPoints

In my previous post on this topic, covered writing the parameterized testcases with @Parameters annotation. If I pick the correct words then that approach was quite messy and not so readable. It required a lot of attention un-necessarily. Well, there is another approach using which you can write parameterized testcases in Junit with help of annotations like @Theory And @DataPoints.

I will take example from previous post, and convert it to new approach. It just make sense because after this we will be able to compare, what got changed and how much it is different from previous approach.

1) Feeding input data with @DataPoints

Here, only annotation has been changed @Parameters to @DataPoints. Rest the concept is same.

Previously, method to feed input was:

@Parameters(name = "Run #Square of : {0}^2={1}")
public static Iterable<Object []> data() {
	return Arrays.asList(new Object[][] { { 1, 1 }, { 2, 4 }, { 3, 19 },
			{ 4, 16 }, { 5, 25 } });
}

Now it is:

@DataPoints
public static int[][] integers()
{
  return new int[][]{{1, 1}, {2, 4}, {3, 9}, {4, 16}, {5, 25}, {}};
}

Please note that you can write the inputs separately using @DataPoint annotation.

@DataPoint
public static int[] input6 = new int[]{6, 36};

@DataPoint
public static int[] input7 = new int[]{7, 49};

I have changed return type from “Iterable<object[]>” to “int[][]” because of slight difference in how these inputs are feed to testcases. You will see the difference in next section.

2) Writing Testcases with @Theory

Structurally, a theory-based class is simpler than a parameterized test class. The class declaration should be annotated with @RunWith(Theories.class), and it must provide two entities:

  1. A data method that generates and returns test data
  2. A theory

The data method must be annotated with @DataPoints, and each theory must be annotated with @Theory. As with an ordinary unit test, each theory should contain at least one assertion.

In previous approach, we wrote the testcase like below:

@Test
public void testUserMapping() {
	// You can use here assert also
	Assert.assertEquals(resultExpected, MathUtils.square(input));
}

Where input and resultExpected were declared as class members and populated using a parameterized constructor. As you can see that testUserMapping() method above does not take any parameter.

In new approach, tests are annotated with @Theory annotation. e.g.

@Theory
public void testSquares(final int[] inputs)
{
  Assume.assumeTrue(inputs[0] > 0 && inputs[1] > 0);
  Assert.assertEquals(inputs[1], MathUtils.square(inputs[0]));
}

You see that parameters are now part of testcase and this is best part of concept. assumeTrue() ensures that parameters are positive numbers and assertEquals() check the function logic which we need to test.

To tun the above testcase, annotate the class in following manner with @RunWith.

@RunWith(Theories.class)
public class JunitTestsWithParameters
{
	//Testcases
}

If you think that some testcases may throw a exception while doing operation, handle them with @Rule annotation and ExpectedException class. A more complete working example is given below:

package test.junit.theory;

import org.junit.Assert;
import org.junit.Assume;
import org.junit.Rule;
import org.junit.experimental.theories.DataPoint;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;

@RunWith(Theories.class)
public class JunitTestsWithParameters
{
   @Rule
   public ExpectedException expectedException = ExpectedException.none();

   @DataPoints
   public static int[][] integers()
   {
      return new int[][]{{1, 1}, {2, 4}, {3, 9}, {4, 16}, {5, 25}, {}};
   }

   @DataPoint
   public static int[] input6 = new int[]{6, 36};
   @DataPoint
   public static int[] input7 = new int[]{7, 49};

   @Theory
   public void testSquares(final int[] inputs)
   {
      Assume.assumeTrue(inputs.length == 2);
      Assume.assumeTrue(inputs[0] > 0 && inputs[1] > 0);
      Assert.assertEquals(inputs[1], MathUtils.square(inputs[0]));
   }

   @Theory
   public void testBlankArrays(final int[] inputs)
   {
      Assume.assumeTrue(inputs.length == 0);
      expectedException.expect(ArrayIndexOutOfBoundsException.class);
      Assert.assertEquals(inputs[1], MathUtils.square(inputs[0]));
   }
}

Run the above testcases and result will look like this:

Junit Theory Example Output
Junit Theory Example Output

Please note that separation of test data from test/theory implementation can have another positive effect apart from brevity: You might start to think about you test data independent of the actual stuff to test.

But at the same time, You should have noticed, however, that there is no means of pairing a specific result with a specific data point. You should use theories when you can express in the form of an assertion the general relationship between a data point and an expected result, and when that relationship will hold true for all data.

So choose between Theory and parameterized testcase carefully with proper consideration. They are not exact alternative to parameterized testcases, rather they complement them.

Happy Learning !!

Was this post helpful?

Join 7000+ Fellow Programmers

Subscribe to get new post notifications, industry updates, best practices, and much more. Directly into your inbox, for free.

2 thoughts on “Junit Parameterized Tests – @Theory And @DataPoints”

Leave a Comment

HowToDoInJava

A blog about Java and its related technologies, the best practices, algorithms, interview questions, scripting languages, and Python.