Transcripted Summary

So far, we've seen three ways of passing parameter values to our test methods.

In some situations, none of those are applicable to our cases, so maybe we need a way to generate some values based on some conditions.

Maybe we need to read some words from the database, process them in a certain way, and then send those processed values to our tests.

Or maybe simply we need some other sorts of processing.

In order to be able to send such processed data to our test methods as parameter values, we can use the @MethodSource annotation.

In this case, we will have to create a method which will process our test data and return it, and the result of that method will be passed as parameter values to our test methods.

Let's see how.

Let's now create a new test method where we want to pass in one single argument, so we want to just pass the value of a single parameter.

Let's say that we want the type of the parameter to be a String.

I will again create a @ParameterizedTest.

I will say 'void' and here I will define the method name as being methodSource_String, because here we will just pass in one parameter and that will be of type String and I will call it again param1.

In order to set the value for param1, we would use this @MethodSource annotation, and here we will need to say, what is the method that will give us those values?

So we'll need to create a separate method that will give us the values.

So let's just say that the method name will be sourceString.


    @ParameterizedTest
    @MethodSource(value = "sourceString")
    void methodSource_String(String param1) {

    }

In order for a method to be eligible to be the provider of values for the parameter of another test method, it needs to return a type Stream or anything that can be converted to a Stream, for example, Collections.

Our favorite Collection, of course, is the List.

So I will give here examples of methods that either return a Stream or a List.

Let's start with the easiest option that we have - namely, with the List.

I will create a method which will be our first provider of data, and it's going to return a List of strings as we wanted, and the name of this method will be the same as the attribute from the @MethodSource annotation.

Now, of course, in our method, we can generate data in any way we want. The important part is that at the end, we need to return a List.

So I'm not going to do any processing here, but generally speaking, when we're using methods, we can do all kinds of processing on the data - we can read it from the database, we can transform some existing input into another form and then return that one, and so on.

But for the purpose of this example, I will just return a List of some values that I will specify in the return.

I will just say //processing done here so that we know that the code in the method can actually be way more than what I'm going to write here.

I'm going to return Arrays.asList, and I'm just going to say "tomato", "carrot", and "cabbage" because we had these in a previous example.


    List<String> sourceString()  {
        //processing done here
        return Arrays.asList("tomato", "carrot", "cabbage");
    }

Right now, my method, which is the source method, is complete, so it's going to return some values.

To test this out in our test method, we're just going to do a System.out of the parameter values that the method receives.


    @ParameterizedTest
    @MethodSource(value = "sourceString")
    void methodSource_String(String param1) {
        System.out.println("param1 = " + param1);
    }

One thing to consider: When we have these source methods, they can be either in the same class as our test methods or in a different class.

If they are in a different class, they will need to be static.

If they're in the same class, we could also declare them as static, unless we've already actually used the lifecycle per class option, because we already had some before or after types of annotations in our tests.

If we don't have that, then this method will need to be static, but I don't want to make the methods static - I prefer to just go ahead and on the class level say @TestInstance(TestInstance.Lifecycle.PER_CLASS), and this way I don't need to specify the static attributes for my parameter values method.

Now, if I run the test, I will see the results.



I will see that my test will run several times and the parameters that it will take will be exactly those values that we have returned from the List of Strings.

Also, on the right hand side, you can see that the output clearly says that, yes, one value was taken for each test run from the List that was returned by this method.

If we prefer to return a String instead of the List, that can be also done easily by creating a different method where the return type will be a Stream of Strings.

And here again, there will be some processing and then the return will just be a Stream of values, and again, in my case, I would just say, "beetroot, apple, and pear".


    Stream<String> sourceStringAsStream() {
        //processing
        return Stream.of("beetroot", "apple", "pear");
    }

We'll just duplicate this method we created as a test method earlier, because I want to have it use the other source parameter - namely the sourceStringAsStream method.

I'm just going to say @MethodSource(value = "sourceStringAsStream").


    @ParameterizedTest
    @MethodSource(value = "sourceStringAsStream")
    void methodSource_StringStream(String param1) {
        System.out.println("param1 = " + param1);
    }

I will now run this method, so that we can see that the values that it will use as parameters will be the ones from the second value provider method.



In this case, we returned a Stream.

What if test methods have a signature with more parameters?

Let's say that, for example, we need to have both a String and a double that we need to pass to each test run.

We will have to create a method that returns either a List of Arguments or a Stream of Arguments.

By arguments, I mean Arguments from the org.junit.jupiter.params package`, so this is something specific to JUnit.



Let's name this method sourceList_StringDouble.

Again, there will be some processing done here because as I mentioned, when we're using a method, we want to generate those values somehow, but when we return them, we need to return, in this, case a List of Arguments.

So I will just return Arrays.asList and we will have to specify several arguments.

Now, in our case, we will specify Arguments.arguments(), as you can see in my overlay.

We will just put in the values for the first pair that we're interested in.

So for example, we want to pass in "tomato" and 2.0.

For now, our List of arguments contains only one element.

We will need to add several ones, so I will again say Arguments.arguments(), where let's say that "carrot" has an associated double value of 4.5.

Then we also need to create a new argument where we put "cabbage" with a corresponding value of 7.8, just for an example.

Now to make this test a little bit cleaner, I'm just going to use "Alt+Enter" on the arguments and "Add a static import" for our arguments.



In my imports section, you can see that we've imported arguments from jupiter.params, and this will make this return a bit shorter.


import static org.junit.jupiter.params.provider.Arguments.arguments;

    List<Arguments> sourceList_StringDouble() {
        //processing
        return Arrays.asList(arguments("tomato", 2.0),
                arguments("carrot", 4.5), arguments(
                        "cabbage", 7.8));
    }

Now, in order to use these values, I will create a new test method and I'm going to say @ParameterizedTest, then @MethodSource(value = ) and set value to the name of this method that we created - sourceList_StringDouble.

The method name will be methodSource_StringDoubleList because we have the return type as a List.

Then we will say String param1 and double param2.


    @ParameterizedTest
    @MethodSource(value = "sourceList_StringDouble")
    void methodSource_StringDoubleList(String param1, double param2) {
        System.out.println("param1 = " + param1 + ", param2 = " + param2);
    }

Now if we run the test, we will just want to do a System.out of those values and let's run the test and see that indeed, we managed to grab two different types of parameters for each method run.



You can see that that is in fact the case.

Our test method signature contains the String and a double.

Our method that provided those values was sourceList_StringDouble.

If we go to that one - I hit "Ctrl" and click on the name specified in value - then we can see that the method which provided the values had a List type of return.

Now exactly the same thing happens if we want to actually return a Stream of Arguments.

Let's call this one sourceStream and again StringDouble.

Here, the only difference would be that we will return a Stream of Arguments.

We will have to just pass in the values, for example, "apple 8.9", and then another argument that is "pear 1.9".


    Stream<Arguments> sourceStream_StringDouble() {
        return Stream.of(arguments("apple", 8.9), arguments("pear", 1.9));
    }

Of course, we will need a test that receives these values.

So I will quickly create a @ParameterizedTest with a @MethodSource where the name of the method providing these values will be sourceStream_StringDouble.

The method will be methodSource_StringDoubleStream and we want, again, a String param1 and a double param2.


    @ParameterizedTest
    @MethodSource(value = "sourceStream_StringDouble")
    void methodSource_StringDoubleStream(String param1, double param2) {
        System.out.println("param1 = " + param1 + ", param2 = " + param2);
    }

Let's do the System.out and let's run the test to see that the Stream values were passed in successfully to our tests.



As you can see, yes, for the first method run, we had "apple" and "8.9", just as is specified in the source method.


# Passing parameters from a different class

One more thing to consider is whether we want the methods that provide the parameters to be placed in a different class from the test class.

In this example, let's just create a new class here in our junit5tests, and I'm just going to call it ParamProvider.



I will just extract this last method I created here, and I will move it to the ParamProvider class.

Now, in order for my tests to now have access to this method, I will need to mark the test as static.

So every time the method is in a different class from the test class, it needs to be static.


package junit5tests;

import org.junit.jupiter.params.provider.Arguments;

import java.util.stream.Stream;

import static org.junit.jupiter.params.provider.Arguments.arguments;

public class ParamProvider {

    static Stream<Arguments> sourceStream_StringDouble() {
        return Stream.of(arguments("apple", 8.9), arguments("pear", 1.9));
    }
}

Now, going back into my test, I will need to update the location where I can find this method, because now it's in a different class and I will have to put in the entire path to the method and I would have to start with the package name.

So in my case, I just have junit5tests as the package name.

In other situations, you may have something that's more like org.junit....

So I would go here and I would say junit5tests., and now I need to specify the name of the class, ParamProvider, and then I would have to put a # between the name of the class and the name of the method.


    @ParameterizedTest
    @MethodSource(value = "junit5tests.ParamProvider#sourceStream_StringDouble")
    void methodSource_StringDoubleStream(String param1, double param2) {
        System.out.println("param1 = " + param1 + ", param2 = " + param2);
    }

So let's see if this works, let's run the test again.

As you can see at the output, yes, the values from the method are the ones that our tests used.



This concludes it for the chapters on using parameterized tests.

We've seen four ways of generating or grabbing our parameter values.

Of course, there are a few more, but these are the most relevant ones for our tests.

These should cover all of our testing needs and scenarios.



Resources



© 2022 Applitools. All rights reserved. Terms and Conditions Privacy Policy GDPR