Skip to content

A way to provide the arguments "name" without actually having it as an argument. #2301

@xenoterracide

Description

@xenoterracide

So I have a parameterized test, and each parameter is a List of Beans where each bean represents a line in a csv file (opencsv). What I want for the actual "parameterized name" is the filename.

I'm thinking something like return

Arguments.of( Named.by(file.getName()), new ArrayList<>() );

@ParameterizedTest
@ArgumentsSource(MyArgumentsProvider.class)
void test( List<AlgorithmVerification> verifications )

Obviously the workaround is to simply pass it as the first parameter and include it as an unused argument. I just have a dislike of unused arguments.

@ParameterizedTest
@ArgumentsSource(MyArgumentsProvider.class)
void test( String filename, List<AlgorithmVerification> verifications )

Activity

juliette-derancourt

juliette-derancourt commented on May 15, 2020

@juliette-derancourt
Member

I had the exact same use case not long ago, and I ended up passing the name as an argument like you did. I agree that it's not ideal...

That being said, I'm not sure how your solution whould deal with the name attribute of @ParameterizedTest? Would the name provided in Arguments replace it? Would you be able to insert it in the name with a placeholder like the other arguments?

xenoterracide

xenoterracide commented on May 15, 2020

@xenoterracide
Author

I think in the proposed solution it would replace it, though you could also do that latter, I'm not certain on the right answer there. TBH I'm not even certain the proposed answer is the right answer.

juliette-derancourt

juliette-derancourt commented on May 15, 2020

@juliette-derancourt
Member

Personally I think I'd prefer the latter.
But I'm not sure it would be useful outside of this (pretty rare) use case... Unless you see other ones?

TBH I'm not even certain the proposed answer is the right answer.

Well I guess there is no right answer anyway 🤓

juliette-derancourt

juliette-derancourt commented on May 22, 2020

@juliette-derancourt
Member

I think the request in #1154 would also solve your problem, wouldn't it?

xenoterracide

xenoterracide commented on May 22, 2020

@xenoterracide
Author

no, seems completely unrelated, since mine was about having to receive a parameter that I'm not using at all, other than to go into the test name. That would still be true even if the parser was improved. #1154 is still a great idea though. Thing is, in my example filename isn't in the array at all, it serves no real purpose other than to describe the file that I'm reading

xenoterracide

xenoterracide commented on May 22, 2020

@xenoterracide
Author

Actually I might have described my proposed solution poorly, a better Api might be something like (obviously some pseudocode)

Arguments.of( Argument.of(filename, passAsArgument = false ))

then it wouldn't actually pass that first argument to the method signature, but it would still be passed to the description.

this is a pretty minor problem though.

marcphilipp

marcphilipp commented on Jun 8, 2020

@marcphilipp
Member

How about a simple Named<T> object that wraps the argument?

xenoterracide

xenoterracide commented on Jun 8, 2020

@xenoterracide
Author

personally not opposed

juliette-derancourt

juliette-derancourt commented on Jun 14, 2020

@juliette-derancourt
Member

How about a simple Named<T> object that wraps the argument?

I like the idea! 👍

added this to the 5.8 Backlog milestone on Aug 7, 2020
sbrannen

sbrannen commented on Aug 7, 2020

@sbrannen
Member

Tentatively slated for 5.8 Backlog to be considered in conjunction with #2375.

5 remaining items

added this to the 5.8 M1 milestone on Sep 25, 2020
marcphilipp

marcphilipp commented on Sep 25, 2020

@marcphilipp
Member

Team decision: Add a Named<T> interface in junit-jupiter-api and add automatic support for injecting the contained payload into parameterized methods directly.

public interface Named<T> {
	static <T> Named<T> of(String name, T payload) {
		return new Named<T>() {
			@Override
			public String getName() {
				return name;
			}
			@Override
			public T getPayload() {
				return payload;
			}
			@Override
			public String toString() {
				return name;
			}
		};
	}
	String getName();
	T getPayload();
}
thomasdarimont

thomasdarimont commented on Oct 2, 2020

@thomasdarimont

This could be really useful when replicating Go style table-driven tests with DynamicTests and local records from Java 15.

import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;

import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;

class TableDrivenTest {

    @TestFactory
    Stream<DynamicTest> tableDrivenTestFromStream() {

        record TestCase(String name, int a, int b, int sum) {

            public void check() {
                assertEquals(sum, a + b, name);
            }
        }

        var testCases = Stream.of(
                new TestCase("test1", 1, 2, 3),
                new TestCase("test2", 2, 2, 4),
                new TestCase("test3", 4, 2, 6)
        );

        return DynamicTest.stream(testCases, TestCase::name, TestCase::check);
    }
}

See: https://twitter.com/thomasdarimont/status/1312100991127285760

With the Named interface from above, one could reduce the plumbing for this a bit.

Btw. if the DisplayName annotation were a bit more flexible one could do something like this:

...
        record TestCase(String name, int a, int b, int sum) {
            @Test
            @DisplayName("$name: $a + $b = $sum") 
            public void check() {
                assertEquals(sum, a + b, name);
            }
        }
...

Here is a gist with some more examples: https://gist.github.com/thomasdarimont/1650ab4d914072bb32d32b58a9ccc571

removed their assignment
on Feb 8, 2021
FlorianCousin

FlorianCousin commented on Nov 19, 2021

@FlorianCousin

What if we have several parameters in a test as follows ?

  @ParameterizedTest
  @ArgumentsSource(DataProvider.class)
  void test(String firstInput, String secondInput) {
    // A test
  }

For such a test, I would get my parameters as follows :

  Stream.of(arguments("first input", "second input"));

It seems that I cannot write

  Stream.of(arguments(Named.of("cool name", "first input", "second input")));

Is there any trick I missed ?

marcphilipp

marcphilipp commented on Nov 21, 2021

@marcphilipp
Member

Named allows you to override the name of one argument.

Hence, you could do

Stream.of(arguments(named("cool name", "first input"), "second input"));

and then use the name attribute in order to use that name to describe the entire invocation:

@ParameterizedTest(name = "{0}")
@ArgumentsSource(DataProvider.class)
void test(String firstInput, String secondInput) {
	// A test
}
FlorianCousin

FlorianCousin commented on Nov 21, 2021

@FlorianCousin

I see, thank you @marcphilipp.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

Relationships

None yet

    Development

    Participants

    @xenoterracide@sbrannen@marcphilipp@thomasdarimont@dmitry-timofeev

    Issue actions

      A way to provide the arguments "name" without actually having it as an argument. · Issue #2301 · junit-team/junit-framework