Is it possible to parameterize a JUnit Jupiter test with beans from a Spring ApplicationContext?
Asked Answered
P

2

11

I would like to write a unit test which is executed for every Spring bean of a given type. JUnit5's parameterized tests offer a lot of possibilities, but I don't know how to inject beans into a method source as it has to be a static method.

Is there a way to determine the parameters of a JUnit5 test based on Spring's application context?

Polecat answered 26/6, 2019 at 6:58 Comment(2)
You could just inject the context and use the Spring Bean's names as parameters and do the lookup in some kind of setup code.Doubleton
But the bean names have to be a static list?Polecat
S
20

For starters, a factory method configured via @MethodSource does not have to be static. The second sentence in the User Guide explains that.

Factory methods within the test class must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS); whereas, factory methods in external classes must always be static.

Thus, if you use @TestInstance(PER_CLASS) semantics, your @MethodSource factory method can be non-static and can therefore access the ApplicationContext injected into the test instance.

Here's an example that demonstrates that for beans of type String, with an intentional failure for the bar bean.

import java.util.stream.Stream;

import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;

@SpringJUnitConfig
@TestInstance(PER_CLASS)
class SpringBeansParameterizedTests {

    @Autowired
    ApplicationContext applicationContext;

    @ParameterizedTest
    @MethodSource
    void stringBeans(String bean) {
        assertEquals(3, bean.length());
    }

    Stream<String> stringBeans() {
        return applicationContext.getBeansOfType(String.class).values().stream();
    }

    @Configuration
    static class Config {

        @Bean
        String foo() {
            return "foo";
        }

        @Bean
        String bar() {
            return "barf";
        }
    }
}

If you don't want to work directly with the ApplicationContext, you can simplify the solution by having the collection of all such beans of a given type (String in this example) injected directly, as follows.

@SpringJUnitConfig
@TestInstance(PER_CLASS)
class SpringBeansParameterizedTests {

    @Autowired
    List<String> stringBeans;

    @ParameterizedTest
    @MethodSource
    void stringBeans(String bean) {
        assertEquals(3, bean.length());
    }

    Stream<String> stringBeans() {
        return this.stringBeans.stream();
    }

    @Configuration
    static class Config {

        @Bean
        String foo() {
            return "foo";
        }

        @Bean
        String bar() {
            return "barf";
        }
    }
}
Suavity answered 26/6, 2019 at 9:36 Comment(1)
Wonderfully detailed answer. Thanks for putting it together.Unclasp
D
0

The usage of the @TestFactory might help.

Actually I stumbled across a post that does a pretty similar (or the same) thing as you do on github.

Let your Test run with the SpringExtenion and use the injected Beans as parameters for our Test.

Doubleton answered 26/6, 2019 at 9:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.