How to test spring configuration classes?

2019-04-24 07:32发布

问题:

I have a spring application whith configuration classes where instance the beans.

Aplication class:

@Configuration
@EnableAspectJAutoProxy
@EnableSpringDataWebSupport
@EnableTransactionManagement
@ComponentScan(basePackageClasses = Application.class)
@PropertySource(value = {"classpath:foo.properties"})
@EnableJpaRepositories(basePackageClasses = Application.class)
@EnableJpaAuditing
public class Application {

    @Inject
    private Environment env;

    @Bean
    JndiTemplate jndiTemplate() {
        return new JndiTemplate();
    }

    @Bean
    public DataSource dataSource() {        
        DataSource dataSource = getDataSource();
        if (dataSource == null) {
            dataSource = new BasicDataSource();
            ((BasicDataSource) dataSource).setUsername(env.getProperty("jdbc.user"));
            ((BasicDataSource) dataSource).setPassword(env.getProperty("jdbc.password""));

            ((BasicDataSource) dataSource).setDriverClassName(env.getProperty("jdbc.driverClassName"));
            ((BasicDataSource) dataSource).setUrl(env.getProperty("jdbc.url"));
        }
        return dataSource;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        EntityManagerFactory factory = entityManagerFactory().getObject();
        return new JpaTransactionManager(factory);
    }

    //....
}

MvcConfiguration class:

@Configuration
@ComponentScan(basePackageClasses = Application.class, includeFilters = @Filter({Controller.class, Component.class}), useDefaultFilters = true)
class MvcConfiguration extends WebMvcConfigurationSupport {
    private static final String MESSAGES = "classpath:/i18n";

    private static final String VIEW_PREFIX = "/WEB-INF/views/";

    @Inject
    private Environment env;

    @Override
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        RequestMappingHandlerMapping requestMappingHandlerMapping = super.requestMappingHandlerMapping();
        requestMappingHandlerMapping.setUseSuffixPatternMatch(false);
        requestMappingHandlerMapping.setUseTrailingSlashMatch(true);

        return requestMappingHandlerMapping;
    }

    @Bean(name = "messageSource")
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasename(MESSAGES);
        messageSource.setCacheSeconds(5);

        return messageSource;
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/").addResourceLocations("/static/**");
    }

    @Bean
    public MultipartResolver filterMultipartResolver(){
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        resolver.setMaxUploadSize(Long.parseLong(env.getProperty("multipart.max.size")));
        return resolver;
    }

    //....

}

And SecurityConfiguration class:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    //....

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //Logout por POST con el valor de token csrf
        http.authorizeRequests()
                .antMatchers("/static/**").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .failureUrl("/login?error=1")
                .loginProcessingUrl("/authenticate")
                .and()
            .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/signin")
                .permitAll();
    }

}

How I can test them with JUnit? How to test the beans are created in the spring context?

回答1:

I believe this is will only be achieved with an Integration Test.

The purpose of Unit Tests are not to check if the whole Spring Context is being created with success.

You can test each configuration method with a Unit Test by using mocks and etc to check if they are ok, but the whole Spring Context thing is an Integration test.

I use to do this Configuration Test by doing what Spring Docs call "Spring Unit Test" (that for me is more like a Integration Test of the Controllers + Views)

The idea is that, if you can get a Spring Context running for a Controller integration Test, than your Configurations are OK.

There is a whole chapter on spring docs on how to do that kind of test. http://docs.spring.io/spring/docs/current/spring-framework-reference/html/testing.html



回答2:

In a word - "don't", that way lays madness.

What you really want is higher level tests that make use of your Spring configuration but are still focused on behaviour not implementation.

For example, looking at your security configuration - you don't really care that the configure method is called, or what it does, what you want to test for is:

  1. Static pages don't require authentication
  2. Other pages do require authentication
  3. Logging in works
  4. Logging out works

Using Spring for DI and security is merely how those things are implemented whereas your tests should be focused on the fact those things actually work.



回答3:

You should be able to test the configuration using the @ContextConfiguration annotation. For Example, the SecurityConfiguration class can be tested like this:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SecurityConfiguration.class) 
class SecurityConfigurationTest {

    @Autowired
    SecurityConfiguration securityConfiguration;

    @Test
    public void passwordEncoderTest() throws Exception {
        final BCryptPasswordEncoder encoder = securityConfiguration.passwordEncoder();
        final String encodedPassword = encoder.encode("password");
        assertNotNull(encodedPassword);
    }
}


回答4:

You could build the context in a JUnit test, provided that all the beans can be instantiated in the test environment. You can use AnnotationConfigApplicationContext and its scan() method to do this.

Such test should be enough for a quick verification of the config. And you can go from there, obtaining beans from the context for a more complex testing.

Couple of pitfalls:

  • Might not work well for lazy-initialized beans
    • You may need to actually request an instance from the context using getBean() for such a class to ensure it is created - you can test that expectation this way
  • May not be always possible, e.g. if you have some database connection as a dependency and don't have access to the database in the test environment
    • You may need to mock such beans for the test's purposes