Rerunning parametrized JUnit test with string array parameter fails - java

While using parametrized JUnit tests in Eclipse, I'm running into a problem when I want to rerun a single test. The tests themselves run fine, and while I can rerun the first test from the context menu, rerunning the second test:
fails with the following message:
java.lang.Exception: No tests found matching [{ExactMatcher:fDisplayName=test[1: A2 --> [Ljava.lang.String;#1e4a7dd4]], {ExactMatcher:fDisplayName=test[1: A2 --> Ljava.lang.String;#1e4a7dd4]] from org.junit.internal.requests.ClassRequest#6c3f5566
I'm pretty sure this is because JUnit doesn't 'like' my arrays; for some context: I'm using this to account for the fact that due to external circumstances, the code under test can produce one of two outcomes for a particular test case.
Here is some code to reproduce this:
package com.stackexchange.toolbox;
import java.util.ArrayList;
import java.util.Arrays;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
#RunWith(Parameterized.class)
public class Tester {
public Tester(String source, Object target) {
this.source = source;
this.target = target;
}
private final String source;
private final Object target;
private static final Object[][] testCases = { { "A1", "B1" }, { "A2", new String[] { "B2", "C2" } } };
#Parameters(name = "{index}: {0} --> {1}")
public static Iterable<Object[]> data() throws Exception {
return new ArrayList<>(Arrays.asList(testCases));
}
#Test
public void test() throws Exception {
if (target instanceof String) {
Assert.assertEquals(source.charAt(1), ((String)target).charAt(1));
} else {
for (String target : (String[])this.target) {
Assert.assertEquals(source.charAt(1), target.charAt(1));
}
}
}
}
Is there an easy way to fix this, perhaps with Lists or variadic arguments? Most of the (100+) test cases are simple 'source', 'target' entries, and I'd like to keep the conciseness of { "A1", "B1" }.

This seems to be a limitation of JUnit4 (at least you get the same error on the command line).
The simplest and straightforward solution would be to migrate from JUnit4 to JUnit5, which would also mean less code:
package com.stackexchange.toolbox;
import java.util.Arrays;
import java.util.stream.Stream;
import org.junit.Assert;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
class Tester {
private static final Object[][] testCases = { { "A1", "B1" }, { "A2", new String[] { "B2", "C2" } } };
#ParameterizedTest(name = "{index}: {0} --> {1}")
#MethodSource("provideArguments")
void test(String source, Object target) {
if (target instanceof String) {
Assert.assertEquals(source.charAt(1), ((String)target).charAt(1));
} else {
for (String targetElement : (String[])target) {
Assert.assertEquals(source.charAt(1), targetElement.charAt(1));
}
}
}
static Stream<? extends Arguments> provideArguments() throws Exception {
return Arrays.stream(testCases).map(Arguments::of);
}
}

Related

Parameterized junit test of custom validation in springboot

I have this validator in springboot which gives error when an integer is not between 1 and 3 and i am using addConstraintViolation to print a message from properties file
public class SizeValidator implements ConstraintValidator<SizeValidation, Integer> {
private int maxSize = 500;
private int minSize = 1;
#Override
public void initialize(SizeValidation constraintAnnotation) {
maxSize = constraintAnnotation.mxSize();
minSize = constraintAnnotation.minSize();
}
#Override
public boolean isValid(Integer givenSize, ConstraintValidatorContext context) {
if (givenSize > maxSize || givenSize<= minPageSize) {
addConstraintViolation(givenSize, "{Size.wrongSize.message}", context);
return false;
}
return true;
}
private void addConstraintViolation(Integer givenSize, String errorMessage, ConstraintValidatorContext context) {
final HibernateConstraintValidatorContext customContext = context.unwrap(HibernateConstraintValidatorContext.class);
customContext.addExpressionVariable("givenSize", givenSize);
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(errorMessage)
.addConstraintViolation();
}
}
and in my validation.properties
Size.wrongSize.message=Value of size ${givenSize} should be between 1
and 3
i wanted to write a Parameterized junit test for it as following but it returns nullpointerexception where am i doing wrong please?
java.lang.NullPointerException
at sizeValidator.addConstraintViolation(SizeValidator.java:33)
intellij says its at position
context.buildConstraintViolationWithTemplate(errorMessage)
.addConstraintViolation();
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.Collection;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
#RunWith(Enclosed.class)
public class SizeValidatorTest{
#RunWith(Parameterized.class)
public static class TestValidEntries {
private SizeValidator validator = new SizeValidator();
private Integer val;
public TestValidEntries(Integer val) {
super();
this.val = val;
}
#Test
public void test() {
assertTrue(isValid(val));
}
#Parameterized.Parameters(name = "{index} Valid: {0}")
public static Collection data() {
return Arrays.asList(
-1, 501
);
}
public boolean isValid(Integer value) {
final HibernateConstraintValidatorContext context = mock(HibernateConstraintValidatorContext.class);
when(context.unwrap(HibernateConstraintValidatorContext.class)).thenReturn(context);
when(context.addExpressionVariable(eq("nonUnique"), anyString())).thenReturn(context);
when(context.getDefaultConstraintMessageTemplate()).thenReturn("template");
final ConstraintValidatorContext.ConstraintViolationBuilder builder = mock(ConstraintValidatorContext.ConstraintViolationBuilder.class);
when(context.buildConstraintViolationWithTemplate("template")).thenReturn(builder);
when(builder.addPropertyNode(anyString())).thenReturn(mock(ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext.class));
return validator.isValid(value, context);
}
}
}
The problem is in the configuration of the context mock.
Your configuration says:
when(context
.buildConstraintViolationWithTemplate(
"template"))
.thenReturn(builder);
But the string you pass in your code under test is: "{Size.wrongSize.message}"
Because this string do not match the mock returns null.
A better approach is to always use unspecific matchers like anyString() in the arrange part of the test method (or in setup) an specific matchers (or values) only in conjunction with Mockito.verify() in the assert part of the test method.

JUnit5: make multiple tests operate on the same object

I want to set up my JUnit5 tests to all operate on the same object. I read large files to use as test data, so I would prefer to read it once and utilize that same data for the rest of the tests.
I've created the following as a simple example where I try to achieve this using a static object ("list") (does not work):
import java.util.List;
import java.util.ArrayList;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
class ExampleTest {
// Single list object to be modified and accessed by the tests
private static List<String> list = new ArrayList<String>();
#BeforeAll
static void setUpBeforeClass() throws Exception {
// None
}
#Test
final void addFoo() {
list.add("foo");
}
#Test
final void addBar() {
list.add("bar");
}
#Test
final void printList() {
System.out.println(list.toString());
assert(list.toString().equals("[foo, bar]"));
}
}
The result of this is a failure of printList() where the list is empty instead of containing [foo, bar].
I have been able to make this work is by moving the methods that add data into the #BeforeAll:
private static List<String> list;
#BeforeAll
static void setUpBeforeClass() throws Exception {
list = new ArrayList<String>();
list.add("foo");
list.add("bar");
}
But having the data importing methods as tests separate from #BeforeAll would be preferred.
#TestInstance(TestInstance.Lifecycle.PER_CLASS) did not work either.
Use #FixMethodOrder annotation.
Refer to this article for example : https://www.mkyong.com/unittest/junit-run-test-in-a-particular-order/
The issue was with the JUnit method ordering (as noted by Abhijay). Utilizing #TestMethodOrder as described in the JUnit5 documentation to appropriately order the tests gave the desired result:
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.MethodOrderer;
#TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class ScratchTest {
private static List<String> list = new ArrayList<String>();
#BeforeAll
static void setUpBeforeClass() throws Exception {
// None
}
#Test
#Order(1)
final void addFoo() {
list.add("foo");
}
#Test
#Order(2)
final void addBar() {
list.add("bar");
}
#Test
#Order(3)
final void printList() {
System.out.println(list.toString());
System.out.println(list.toString().equals("[foo, bar]"));
assert(list.toString().equals("[foo, bar]"));
}
}

iterative testing using junit

I have a bunch of test inputs that I would like to run and compare the output with expected:
#Test
void test () throws IOExeption {
for (File i : readDir()) {
File out = foo(i);
assertEquals(FileUtils.readLines(expected), FileUtils.readLines(out));
}
}
I would like to run the tests using JUnit. But if I do it like the above then JUnit will stop after encountering the first test failure. Is there a better way to do this other than making each file its own test case like below?
#Test
void test1 () throws IOExeption {
File i = readFile("1.txt");
File out = foo(i);
assertEquals(FileUtils.readLines(expected), FileUtils.readLines(out));
}
#Test
void test2 () throws IOExeption {
File i = readFile("2.txt");
File out = foo(i);
assertEquals(FileUtils.readLines(expected), FileUtils.readLines(out));
}
I think, that you could use Parameterized. This is standard feature of JUnit. Below you can see an example.
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.Collection;
#RunWith(Parameterized.class)
public class Foo {
#Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] { { "1.txt" }, { "2.txt" } });
}
#Parameterized.Parameter // first data value (0) is default
public /* NOT private */ String fileName;
#Test
public void test() {
File i = readFile(fileName);
File out = foo(i);
assertEquals(FileUtils.readLines(expected), FileUtils.readLines(out));
}
}
I have not found JUnit official documentation about this, but you can find more details e.g. in this tutorial: https://www.tutorialspoint.com/junit/junit_parameterized_test.htm

Parameterized Junit test on Android with NULL parameter

I am trying to write a JUNIT test with Parameterized test runner on android.
When i run it as part of AndroidTest in Android Studio it give me following error for first test:
ExampleTEst: Trying to set intVal with the value null that is not the
right type (String instead of Integer).
But when i run the same code as simple JUNIT test ( under android test in Android Studio) , it works fine.
Here is complete code of the example :
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.Collection;
import static junit.framework.Assert.assertEquals;
#RunWith(Parameterized.class)
public class ExampleTEst {
#Parameterized.Parameters
public static Collection<Object[]> params() {
return Arrays.asList(new Object[][]{
{//Test # 0 :
null,
"JOJO"
}
,
{//Test # 1 :
1,
null
}
});
}
#Parameterized.Parameter
public Integer intVal;
#Parameterized.Parameter(value = 1)
public String StringVal;
public static final Object[] resultsObjs = {null, "JOJO", 1, null};
public static int counter = 0;
#Test
public void aTest() {
assertEquals((Integer) resultsObjs[counter], intVal);
assertEquals((String) resultsObjs[counter + 1], StringVal);
counter += 2;
}
}

Passing arrays to Parameterized JUnit

I am new to parameterized feature of JUnit 4.x and having a problem. My parameterized test consists of 3 integer arrays and I am having difficulty of how to declare them. What I have below generates run-time error:
testGeneral[0] caused an ERROR: argument type mismatch
argument type mismatch
java.lang.IllegalArgumentException
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
testGeneral[1] caused an ERROR: argument type mismatch
argument type mismatch
java.lang.IllegalArgumentException
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
Here is my code:
#RunWith(Parameterized.class)
public class MyArrayTest {
private Integer[] inputList1;
private Integer[] inputList2;
private Integer[] expectedList;
public MyArrayTest(Integer[] li1, Integer[] li2, Integer[] expected) {
// ========> Runtime error happens here. <=========
this.inputList1 = li1;
this.inputList2 = li2;
this.expectedList = expected;
}
#Parameterized.Parameters
public static Collection testCases() {
return Arrays.asList(new Object[][][] {
{{1,1,1}, {2,2,2}, {3,3,3}},
{{2,2,2}, {3,3,3}, {4,4,4}}
});
}
#Test
public void testGeneral() {
// Do some test with this.inputList1, this.inputList2,
// and verify with this.expectedList
// I am not even getting here yet.
}
}
I appreciate your help to correctly passing the three arrays to my tests.
The reason why it is failing is because your test expects Integer arrays whereas you are passing Object type. So you are expanding the type. Try this:
#Parameterized.Parameters
public static Collection testCases() {
return Arrays.asList(new Integer[][][] {
{{1,1,1}, {2,2,2}, {3,3,3}},
{{2,2,2}, {3,3,3}, {4,4,4}}
});
}
This solution uses junitparams, implements junitparams.converters.Converter and parses list of long values as parameters.
package example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import junitparams.converters.ConversionFailedException;
import junitparams.converters.Converter;
import junitparams.converters.Param;
#RunWith(JUnitParamsRunner.class)
public class LongArrayParameterTest {
#Parameters({ "0|10", "1|10;20;30" })
#Test
public void test(final long otherParameter, #LongArrayParam final long[] expected) {
System.out.println(Arrays.stream(expected).boxed().map(l -> Long.toString(l)).collect(Collectors.toList()));
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
#Param(converter = LongArrayConverter.class)
public #interface LongArrayParam {
}
public static class LongArrayConverter implements Converter<LongArrayParam, long[]> {
#Override
public void initialize(final LongArrayParam annotation) {
}
#Override
public long[] convert(final Object param) throws ConversionFailedException {
final String str = (String) param;
final String[] longStrings = str.split(";");
return Arrays.stream(longStrings).mapToLong(s -> Long.parseLong(s)).toArray();
}
}
}
This parser does not support empty list.

Categories