Search code examples
javaspringjunit4

Test Spring beans with different constructor args over JUnit using @ContextConfiguration


Description of the problem:


I would like users to configure spring beans with custom config files over spring XML configs, like this: Note that only Strings should be configured by the user, all other beans should be @Autowired without the user knowing it!

<bean class="com.my.group.Provider">
    <constructor-arg value="config1.proprietary"/>
    <constructor-arg value="config2.proprietary"/>
</bean>

The Provider object looks (simplified) as follows:

public class Provider {
    @Autowired
    private Foo foo;
    private final String[] configNames;

    public Provider(final String... configs) {
        this.configNames = Preconditions.checkNotNull(configs, "Provided configs must not be null!");
    }

    public List<Configs> getConfigs() {
         return new foo.create(configNames); // here is more logic that I would actually like to test... (not just methods called on foo)
    }
}

My question is:

How can I test this solution with various different string inputs, so that all tests can go into one JUnit Test class? Btw: I would like to avoid reflections...

(The unit tests below show what I mean. And they are already capable to do what I want, but they use reflections.)


What I did so far

is using reflections to change the field content afterwards, but tbh that is not sexy at all:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {ProviderTest.MyContext.class})
public class ProviderTest {

    @Autowired
    private Provider sut;

    @Test
    public void provide_oneConfig() throws NoSuchFieldException, IllegalAccessException {
        setConfigFilesViaReflection(sut, "config1.proprietary"");
        // When
        List<Config> configs = sut.getConfigs();

        // Then
        assertEquals(1, configs.size());
    }

    @Test
    public void provide_twoConfigs() throws NoSuchFieldException, IllegalAccessException {
        setConfigFilesViaReflection(sut, "config1.proprietary", config2.proprietary");
        // When
        List<Config> configs = sut.getConfigs();

        // Then
        assertEquals(2, configs.size());
    }

    private void setConfigFilesViaReflection(final Provider sut, final String... configs) throws NoSuchFieldException,
            IllegalAccessException {
        Field configNamesField = Provider.class.getDeclaredField("configNames");
        configNamesField.setAccessible(true);
        configNamesField.set(sut, configs);
    }

    @Configuration
    public static class MyContext {
        @Bean
        Provider provider() {
            return new Provider("willBeOverridenByReflection");
        }

        @Bean
        Foo foo() {
            return new Foo(); // this one got mocked in my test
        }
}

Solution

  • Sometimes asking a questions helps to search harder.

    The @Qualifier / @Resource annotation make it possible to create several beans, and choose them per test like that:

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = {ProviderTest.MyContext.class})
    public class ProviderTest {
    
        @Autowired
        @Qualifier("bar") // could also be @Resource (without @Autowired)
        private Provider sut;
        @Resource(name="baz")
        private Provider sut2; // could also be @Qualifier(with @Autowired)
    
        @Test
        public void provide_oneConfig() throws NoSuchFieldException, IllegalAccessException {
            // When
            List<Config> configs = sut.getConfigs();
    
            // Then
            assertEquals(1, configs.size());
        }
    
        @Test
        public void provide_twoConfigs() throws NoSuchFieldException, IllegalAccessException {
            // When
            List<Config> configs = sut2.getConfigs();
    
            // Then
            assertEquals(2, configs.size());
        }
    
        @Configuration
        public static class MyContext {
            @Bean("bar")
            Provider providerBar() {
                return new Provider"config1.proprietary");
            }
            @Bean("baz")
            Provider providerBaz() {
                return new Provider("config1.proprietary", "config2.proprietary");
            }
    
            @Bean
            Foo foo() {
                return new Foo(); // this one got mocked in my test
            }
    }
    

    Found my answer here: Autowiring two different beans of same class