JUnit 5 – When to use @ValueSourceApril 4, 2023
This article belongs to the Managing Test Data series.
I will show you different ways to smartly use testing frameworks features when the date changes but the test output is the same.
In this article, you will see one use case for the
MethodSource feature, which we will use in the same test class.
In a nutshell, the MethodSource feature allows you to use the data in your tests from a method.
@MethodSource feature will use factory methods internally (in the same test class) or externally (from a different class, not necessarily a test one). These methods are the ones that will have the data used in the test. They must be
static and return a Stream where we can type it as a single object or an Arguments object in the case of multiple data. I recommend you also read the JUnit 5 explanation of the MethodSource.
To use the MethodSource feature you must:
- use the
@MethodSourceannotation, which will be connected to a factory method
- create a static factory method that:
- returns a
- have Arguments, expressing as many parameters as you need
- returns a
- match the Arguments parameters in the test method
The application of the MethodSource
Good news: you can use it at any time! This is one of the most flexible data-driven methods in JUnit 5.
@ValueSource does not apply to you given more than one data/attribute to handle, go for the
We are calling the MethodSource here internal as you will create the factory method in the same test class.
Is it possible to create a separation, this will be explained in another article.
Let’s say we need to check if a given date list is from 2023.
I advise starting with the data you need, then creating the test class.
dateList method, in line 10, is the factory method. It expects a
Arguments that will have the data to use in the tests.
In line 11, a Stream of
Arguments is returned, where we can add any Argument we want. In this example line 12 to 15 show that the argument (or data) is a
LocalDate, so each
Argument.argument() is a different set of data that will be used n the test, so the test will run 3 times.
We must associate the factory method with the test, and this is done by adding the
@MethodSource annotation and the factory method name as a parameter. We can see it in line 5 as the test method
myTest() has a parameter
The last thing to do is to add, in the test method, the parameter that matches the
Argument. In this case, the data is a
LocalDate object, so we need to add the same parameter type. You can see it in line 6.
This test does a simple check: if all the
Arguments (data) are in 2023.
Case 1: you have a single data type that’s not the supported one in the @ValueSource
In the JUnit5 – When to use @ValueSource we learned that it supports
java.lang.Class. So if your data type is not one of these, go for the
This is precisely what we have in the Simple example: we need to use the
LocalDate class in the test that is not supported in the
Case 2: you have a set of data to use
When you have a different set of data that belongs to the same context the
@MethodSource is a good choice because you can add as much as arguments as you need in the factory method, which will be associated with the test.
Let’s imagine you have an algorithm where different inputs generate different results.
In the example below we are searching for a payment status, which will return a message.
Lines 13 to 16 show that we have two arguments: a
status and a
message. We are associating these arguments in the test method, as parameters, in line 6.
Line 7 uses the
status to search for payment in an audit service, returning a message as
Line 8 asserts that the
message matches the expectation, which is associated in the
statusList (the factory method) by the pair of arguments, for example,
STATUS_PROCESSING and the
"The Payment is being processed.".
You can, based on your context, add as many arguments as you want, meaning as many parameters in the
This is a silly example only to give you an idea of what you can do with the @MethodSouce feature.
Case 3: you can use any object to hold the data
@MethodSource can use
Arguments, we just learned. The arguments can be any object. In fact, we are using the
Status object (an enum) and the String object in the previous example.
You can also add your custom object to transport the data to the test. Also, remember you can use the data-driven approach to test edge cases/negative scenarios.
Let’s assume you have a Simulation object that has a lot of attributes, and two of them are
amount. They have the following rule:
|installments||Minumum value: 2||Installments must be less than or equal to 48|
|installments||Maximum value: 48||Installments must be less than or equal 48|
|amount||Minimum value: 1.000||Amount must be greater than or equal to 1.000|
|amount||Maximum value: 40.000||Amount must be greater less or equal to 40.000|
We can now test the edge cases based on the rules. We need to create the factory method using the
Simulation class holding the data based on the edge cases, plus the error message to assert it in the test. During a post request, we will send the Simulation object, expecting an HTTP 422 – Unprocessable Entity, where we will validate the response body matching with the expected error message.
The code below is generic and not based on any API Testing framework:
Lines 13 to 16 are
Simulation objects holding the different data values for the edge cases.
Lines 19 to 22 add the
Simulation objects as
Arguments, associating them with the related error message.
Line 7 creates a post request, expecting a
Line 9 asserts that the response status code is 422 (Unprocessable Entity)
Line 10 asserts that the response body has an error message as the expected one by the factory class.
Is there any possible problem using the Internal Method Source?
Actually, no 🙂
What can happen is that you might have a factory method that could be used by another test.
We will understand how to solve this problem in the upcoming “External Method Source” and “Argument Provider” articles.
When should I use the Internal Method Source?
- the @ValueSource is not sufficient to create your test
- when you have more than 2 or more sets of data to use in your test
What are the benefits of using the Internal Method Souce?
- readability: you will understand the data required without the necessity to go to any other class or method to see its data
- edge-cases: you will be able to create the happy paths and edge cases given the ability to add more arguments to the factory method
- isolation: your factory method will be available only in your test, where you can have more control to its data