How to generate multiple test method from JSON? - java

I have a huge JSON-File which contains testcases. (No I don't wanna show it here because it doesn't matter in this case to know the file)
I parse the json file to my junit test - that works fine.
But I've got 50 testcases and if I want to show each in Junit like: "test 0 from 50 passed"
and have a list like: test 1 passed, test 2 passed, test 3 failed..
I have to put each testcase into a method. How can I dynamically do this? Is this possible in Junit? Because when I'm parsing the json, I don't know how many cases I have.

JUnit has direct support for CSV files, which means you can import and use them easily using #CSVFileSource.
However, since your case does not involve CSV files, I tried to create parametrized tests in JUnit 5 using JSON files.
Our class under test.
public class MathClass {
public static int add(int a, int b) {
return a + b;
}
}
Here's the JSON file I am using.
[
{
"name": "add positive numbers",
"cases": [[1, 1, 2],[2, 2, 4]]
},
{
"name": "add negative numbers",
"cases": [[-1, -1, -2 ], [-10, -10, -20 ]]
}
]
So, in JUnit 5 there is an annotation called #MethodSource which gives you the opportunity to provide arguments to your parametrized test. You only need to provide the method name. Here's my argument provider method.
#SneakyThrows
private static Stream<TestCase> getAddCases() {
final ObjectMapper mapper = new ObjectMapper();
TypeReference<List<Case>> typeRef = new TypeReference<>() {};
final File file = new File("src/test/resources/add-cases.json");
final List<Case> cases = mapper.readValue(file, typeRef);
return cases.stream()
.flatMap(caze -> caze.getCases()
.stream()
.map(el -> new TestCase(caze.getName(), el)));
}
In the code above, the class Case is used to map from the json object to Java Object and since the "cases" field is a multidimensional array, to represent each test case there is a class called TestCase. (Overall, this is not important for you, since you already are able to parse it, but I wanted to put it here anyway).
Finally, the test method itself.
#ParameterizedTest(name = "{index} : {arguments}")
#MethodSource("getAddCases")
void add_test(TestCase testCase) {
final List<Integer> values = testCase.getValues();
int i1 = values.get(0);
int i2 = values.get(1);
int e = values.get(2);
assertEquals(e, MathClass.add(i1, i2));
}
#ParametrizedTest annotation takes a name argument where you can provide a template for the test names. I just played around with the toString method of TestCase class to achieve a better description for each test case.
#Override
public String toString() {
return String.format("%s : (%s, %s) ==> %s", name, values.get(0), values.get(1), values.get(2));
}
And voila!

Related

Combine #MethodSource and #CsvSource on a Junit5 parameterized test

What would be the best approach to combine multiple arguments source for parameterized testing.
My scenario is something like this
#ParameterizedTest(name = "{index} => \"{0}\"")
#MethodSource("getEndpointsProvider")
#CsvSource(nullValues = "null",
value = {
"whenNullValues, null, BAD_REQUEST",
"whenNaNValues, uyiwfq, BAD_REQUEST",
"whenUnauthorized, 12345, FORBIDDEN",
"whenNonExisting, -1, NOT_FOUND"
})
#Test
void testEndpointsNegativeCases(Method endpoint, String case, String input, String expected)
{
//... process input args
//... assertThrows
}
The idea is to make a L x R combination, so Number of endpoints X CsvSource lines, and to keep it in a way that is perfectly clear for the reader which cases are tested and the expectations (ideally, in the annotations).
Extending getEndpointsProvider() to return a stream of multiple args with the combined data is an option, but I would like to know if there's a cleaner way.

How to create JUnit tests for different input files as separate cases?

Right now, I have around 107 test input cases for my interpreter, and I have my JUnit tester set up to manually handle each case independently so as to not lump them all together. That is, if I use a loop to iterate over the test files as such
for (int i = 0; i < NUM_TESTS; i++) {
String fileName = "file_" + (i + 1) + ".in";
testFile(fileName);
}
JUnit will create one giant test result for all 107 tests, meaning if one fails, the entire test fails, which I don't want. As I said, right now I have something like
#Test
public static void test001() {
testFile("file1.in");
}
#Test
public static void test002() {
testFile("file2.in");
}
While this works, I imagine there's a much better solution to get what I'm after.
You can use #ParameterizedTest with #MethodSource annotations.
For exemple :
#ParameterizedTest
#MethodSource("fileNameSource")
void test(final String fileName) {
testFile(fileName);
}
private static Stream<String> fileNameSource() {
return IntStream.range(0,NUM_TESTS).mapToObj(i -> "file_" + (i + 1) + ".in");
}
Check the documentation at https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests
For each params returned by fileNameSource(), the corresponding test will be considered as a different case.
You have to define own structure based on your need,
one way to define your input to json file in like list of values as below.
{
[
"value1",
"value2"
]
}
Read this value when you test case execute with the help of object mapper.
objectMapper.readValue(fixture("filePathName.json"),CustomInput.class);
Where CustomInput would be something like below.
public class CustomInput {
List<String> values;
}
You can keep increase & decrease your inputs in json.

JUnit4 Empty Parameterized Test

I've build a xquery Testframework, sometimes I want to test header and inbound and sometimes just Header for example.
I've got therefore 4 Testcases:
headerAndBody,
headerAndBodyTestsuite,
Header,
HeaderTestsuite
I search the directory for data and construct the tester from it.
And here is the Error:
#Test
public void someTest() throws Exception{
if (listHdrInbPayTestSuites.size() > 0) {
assertTrue(buildTest.testHdrInbPay(testSuiteIdentifier));
}
}
The list is EMPTY! but JUnit still says the test ran twice. How can I fix it to say it's an empty test?
Edit:
#Parameter
public String testSuiteIdentifier; (which actually has 2 items)
Edit:(One Testsuite can contain several testcases, so I can't just take another parameter, because I want to identify them by Testsuite name.)
Temporary solution:
Found a workaround to ignore these tests:
#Before
public void shouldRunTest() {
Assume.assumeTrue(listHeaderAndBodyTestSuites.size() > 0);
}
I'm not really satisfied with this solution since the information "test skipped" can be misleading.
Any other approaches/ideas?
JUnit does not support the concept of an empty test. An ignored test is the concept that comes closest to it.
In order to not increase the test count you have to ensure that the data values are not part of the parameters. This means you have to remove them within your #Parameters method. Here is an example that removes some data values for the Fibonacci test example of the Parameterized runner's documentation.
#Parameters
public static Iterable<Object[]> data() {
Collection<Object[]> currentData = Arrays.asList(
new Object[][] {
{ 0, 0 },
{ 1, 1 },
{ 2, 1 },
{ 3, 2 },
{ 4, 3 },
{ 5, 5 },
{ 6, 8 }
}
);
return currentData.stream()
.filter( dataSet -> dataSet[1] > 3 )
.collect( Collectors.toList() );
}

Bind Datapoints in JUnit Theory to a particular variable

I have following theory to test. In the code I want variable a to be Even and variable b to be odd
#RunWith(Theories.class)
public class TestJunit{
// add the error
#DataPoints
public static Integer[] integersOdd() {
return new Integer[]{1, 3, 5};
}
#DataPoints
public static Integer[] integersEven() {
return new Integer[]{2, 4, 6};
}
#Theory
public void testAdd(Integer a , Integer b) {
...
}
}
For now I am using assumeTrue and a validation function as in:
public boolean validateInput(Integer a, Integer b){
Set<Integer> even = new HashSet<Integer>(Arrays.asList(integersEven()));
Set<Integer> odd = new HashSet<Integer>(Arrays.asList(integersOdd()));
return (even.contains(a) && odd.contains(b));
}
Modified Theory:
#Theory
public void testAdd(Integer a , Integer b) {
Assume.assumeTrue(validateInput(a,b));
System.out.println("a="+a+", b="+b);
assertTrue(a+b>-1);
// add any test
}
It is a very dirty way as Java will pick all the combinations and will discard the inputs at assumeTrue. What If I have 10 theories with 10 datapoints? Java will try 100 combinations where I wanted only 10!
Is there neat way to do so? May be some annotation to tell JUnit to pick values for variables from which DataPoint?
Edit:
Another way I found is to use Test Generators. I am using JUnit-QuickCheck [Read Here] to generate random data according to the range required by my variables. Then I encapsulate them in a class and pass this object into my theory to test.
JUnit 4.12 allows for named data points in theories. Here's the original pull request, and here are the release notes for 4.12 - look for "Added mechanism for matching specific data points".

Retrieve Junit test expected result

I'm writing Junit test cases for a bunch of classes; each of them has a handful of method to test. The classes I'm about to test look like the following.
class A{
int getNth(int n);
int getCount();
}
class B{
int[] getAllNth(int n);
int getMin();
}
I store the expected result for each class.method() in a file. For example, in a CSV,
A; getNth(1):7; getNth(2):3; getCount():3
B; getAllNth(2):[7,3]; getAllNth(3):[7,3,4]; getMin():3
My question is how can retrieve those value easily in test cases. I hope to pass the method call A.getNth(2) to a class that can build a string "A.getNth(2)"
If the format I store the data is not ideal, free feel to give suggestion on that as well.
It sounds like you might want to use Fitnesse?
Not sure about JUnit, but here is how you would do it with TestNG, using data providers:
#DataProvider
public Object[][] dp() {
return new Object[][] {
new Object[] { 1, 7 },
new Object[] { 2, 3 },
};
}
#Test(dataProvider = "dp")
public nthShouldMatch(int parameter, int expected) {
Assert.assertEquals(getNth(parameter), expected);
}
Obviously, you should implement dp() in a way that it retrieves its values from the spreadsheet instead of hardcoding them like I just did, but you get the idea. Once you have implemented your data provider, all you need to do is update your spreadsheet and you don't even need to recompile your code.

Categories