Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce support for parameterized classes #4342

Merged
merged 61 commits into from
Feb 28, 2025

Conversation

marcphilipp
Copy link
Member

@marcphilipp marcphilipp commented Feb 25, 2025

Overview

This PR introduces @ParameterizedClass which builds on @ContainerTemplate and allows declaring a top-level or @Nested test class as a parameterized container to be invoked multiple times with different arguments. The same @...Source annotations as for @ParameterizedTest may be used to provide arguments via constructor or field injection.

Resolves #878.


I hereby agree to the terms of the JUnit Contributor License Agreement.


Definition of Done

@marcphilipp marcphilipp self-assigned this Feb 25, 2025
@marcphilipp marcphilipp force-pushed the marc/878-parameterized-containers branch from 3b1836d to 6de9f4e Compare February 27, 2025 08:49
@georgberky
Copy link

georgberky commented Feb 28, 2025

This works very well for me. My main use case is to test multiple implementations of the same interface with the same suite of tests:

@ParameterizedContainer
@MethodSource("instances")
public class BranchByAbstractionTest {

    public static Stream<Arguments> instances() {
        return Stream.of(
                new FirstImplementation(),
                new SecondImplementation()
        ).map(Arguments::arguments);
    }

    private final Abstraction abstraction;

    public BranchByAbstractionTest(Abstraction abstraction) {
        this.abstraction = abstraction;
    }

    @Test
    void somethingAboutTheAbstraction() {
        assertEquals(23, abstraction.doSomething());
    }
}

I also love that constructor injection works 😍 . Being able to use the same arguments providers is really helpful.

@georgberky
Copy link

georgberky commented Feb 28, 2025

Maybe for future improvements:

Would it be possible and an improvement for readability to have the @MethodSource directly on the contrustor/field?

The same argument would probably be valid for regular parameterized tests, i.e. should the presence of an argument provider on a class/field/method be sufficient to make it parameterized without requiring the additional @ParameterizedTest or @ParameterizedContainer? For test methods we could keep the usual @Test then.

@marcphilipp marcphilipp changed the title Introduce support for parameterized containers Introduce support for parameterized classes Feb 28, 2025
@marcphilipp
Copy link
Member Author

marcphilipp commented Feb 28, 2025

This works very well for me

I'm glad to hear it! 🙂

My main use case is to test multiple implementations of the same interface with the same suite of tests

If you have a single parameter, you don't have to wrap the arguments in Arguments but can just write:

@ParameterizedClass
@MethodSource("instances")
class BranchByAbstractionTest {

    static List<Abstraction> instances() {
        return List.of(
                new FirstImplementation(),
                new SecondImplementation()
        );
    }

    private final Abstraction abstraction;

    BranchByAbstractionTest(Abstraction abstraction) {
        this.abstraction = abstraction;
    }

    @Test
    void somethingAboutTheAbstraction() {
        assertEquals(23, abstraction.doSomething());
    }
}

Or using @FieldSource:

@ParameterizedClass
@FieldSource("instances")
class BranchByAbstractionTest {

    static final List<Abstraction> instances = List.of(
            new FirstImplementation(),
            new SecondImplementation()
    );

    private final Abstraction abstraction;

    BranchByAbstractionTest(Abstraction abstraction) {
        this.abstraction = abstraction;
    }

    @Test
    void somethingAboutTheAbstraction() {
        assertEquals(23, abstraction.doSomething());
    }
}

If you can use Java records:

@ParameterizedClass
@FieldSource("instances")
record BranchByAbstractionTest(Abstraction abstraction) {

    static final List<Abstraction> instances = List.of(
            new FirstImplementation(),
            new SecondImplementation()
    );

    @Test
    void somethingAboutTheAbstraction() {
        assertEquals(23, abstraction.doSomething());
    }
}

Would it be possible and an improvement for readability to have the @MethodSource directly on the constructor/field?

The same argument would probably be valid for regular parameterized tests, i.e. should the presence of an argument provider on a class/field/method be sufficient to make it parameterized without requiring the additional @ParameterizedTest or @ParameterizedContainer? For test methods we could keep the usual @Test then.

I think we would still require @ParameterizedTest or @ParameterizedClass for clarity. But auto-discovering @MethodSource methods for @ParameterizedClass sounds interesting, maybe via a new @Arguments annotation on fields or methods. Technically, this could be achieved by implementing a new ArgumentsProvider that checks for those fields and registering it on the @ParameterizedClass composed annotation.

@marcphilipp
Copy link
Member Author

Team decision: Merge after renaming the annotation to @ParameterizedClass. Make it @Inherited in a subsequent PR.

@marcphilipp marcphilipp merged commit c4faa6c into main Feb 28, 2025
15 checks passed
@marcphilipp marcphilipp deleted the marc/878-parameterized-containers branch February 28, 2025 15:22
@georgberky
Copy link

Thanks, Marc! I haven't seen @FieldSource before. That's really cool 🙏🏻 .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Introduce support for parameterized classes
2 participants