No more driver management in Selenium WebDriver
January 27, 2024One of the most underrated Maven configurations: maven.config
February 27, 2024Introduction
This article belongs to the Managing Test Data series.
I will show you different ways to smartly use testing framework features when the date changes but the test output is the same.
In this article, you will see one use case for the Argument
Source feature, in which we will use the data source from a different class.
ArgumentsSource
The @ArgumentsSource
annotation expects a class, where it must implement the ArgumentProvider interface which will have a single method that will return a Stream of Arguments (Stream<Arguments>
) in the same way, you create the data factory method using the @MethodSource.
At this point, it’s good to explain, at a high level, what’s the internals.
What’s an Argument
Argument is an abstraction that provides access to an array of objects to be used for invoking a @ParameterizedTest
method.
Think about arguments as the data, implicitly or explicitly, used by any parameterized source of arguments. All the annotations presented in this article series are sources of arguments.
Why explicitly? Because sources like @NullSource, and @EmptySource do not require any parameters. Why explicitly? Because sources like the @ValueSource get the supported values from the annotation parameter or the @MethodSource which gets the factory method and extracts its arguments.
What’s an ArgumentsSource
All the sources of arguments have this pattern: The @ArgumentsSource annotation is used to indicate it @ArgumentsProvider which is responsible for providing a stream of arguments to be passed and used by the @ParameterizedTest
.
In short, each source of arguments implements its ArgumentsSource
.
The ArgumentsSource
is a way to provide a custom data implementation the way you want by implementing the ArgumentsProvider interface.
Most of the implemented ArgumentsProvider
get the ExtensionContext
object and extracts the test method, and then the parameter types. You can see this approach in the EmptyArgumentsProvider that checks if each supported type is empty.
The application of the ArgumentsSource
There’s one example, and it will be always like this.
First, we define a class that will hold its data, but different from the External MethodSource approach, this class must implement the ArgumentProvider
interface.
public class ProductsDataArgumentProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) {
return Stream.of(
arguments("Micro SD Card 16Gb", new BigDecimal("6.09")),
arguments("JBL GO 2", new BigDecimal("22.37")),
arguments("iPad Air Case", new BigDecimal("14.99"))
);
}
}
On the first line, you can see that we are implementing the ArgumentProvider interface. This will make you implement the provideArguments
method, which you can see in detail on line 4.
Another difference compared to the External MethodSource is that the method has the EXtensionContext parameter, which will understand what are the class, method, and objects on this data factory method.
The code will add two different parameters to the test: a String
related to a product name and a BigDecimal
related to its amount.
Below you can see one of the possible tests.
class JUnitArgumentsProviderTest {
private static final String MAXIMUM_PRICE = "30.0";
@DisplayName("Products should not exceed the maximum price")
@ParameterizedTest(name = "product ''{0}'' of amount ${1} does not exceeds $" + MAXIMUM_PRICE)
@ArgumentsSource(ProductsDataArgumentProvider.class)
void cheapProducts(String product, BigDecimal amount) {
assertThat(product).isNotEmpty();
assertThat(amount).isLessThanOrEqualTo(new BigDecimal(MAXIMUM_PRICE));
}
}
This test asserts that all the expected product amounts are less or equal to $ 30, and uses the data from the ProductsDataArgumentProvider
class we just created as the data to be used in the test. Line 7 shows you this.
Line 8 shows the two parameters used: String product
and BigDecimal amount
which matches the data type we have in the ProductsDataArgumentProvider
class.
When should I use the ArgumentsSource?
That’s a tricky question because you might find the External MethodSource and the ArgumentsSource the same.
Of course, they have a few implementation differences, and I will highlight them:
ArgumentsSource | External MethodSource | |
Implementation | External class, where the data factory method must be 1-1 as it implements the ArgumentsProvider interface | External class, where we can have multiple data factory methods |
Pros | The association of the @MethodSource annotation is on its fully qualified path, making it slightly difficult for refactoring and fast class access You can make an explicit argument conversion You can extend its behavior by extending the AnnotationBasedArgumentsProvider class | Central point for related data factory methods |
Cons | One data factory method per class | The association of the @MethodSource annotation is on its fully qualified path, making it slightly difficult for refactoring and fast class access |
Now it’s up to you to decide which approach you can take.
This author prefers the @ArgumentsSource
as it’s more flexible and separates well the data responsibilities.
1 Comment
The one reason to use @ArgumentSource! If you use a @MethodSource in a @Nested test, you need to fully qualify the method source, which is hard to get right, especially since the IDE can’t autocomplete it. @ArgumentSource doesn’t have these problems.