Wiring a Test in Spring Boot 2.1 without enabling bean overriding

Viewed 243

I have code with named beans

@Bean
@Named("heimdall-uri-supplier")
public URISupplier heimdallEndpointSupplier(CredentialsClientConfig config, EnvInfo envInfo) {
    ....
}

@Named("vault-uri-supplier")
@Bean
public URISupplier vaultURISupplier(EnvInfo envInfo, CredentialsClientConfig config) {
   ....
}

They are explicitly Named because I want specific implementations injected into different consuming classes. These classes also use @Named.

In tests, prior to Spring Boot 2.1

@Bean
@Primary
@Named("heimdall-uri-supplier")
public URISupplier heimdallEndpointSupplier(CredentialsClientConfig config, EnvInfo envInfo) {
    return mock of some sort
}

@Named("vault-uri-supplier")
@Bean
@Primary
public URISupplier vaultURISupplier(EnvInfo envInfo, CredentialsClientConfig config) {
   return mock of some sort
}

worked great.

Now, of course spring boot 2.1 disables overriding. I know I can reenable it, but I'd rather not in theory do so.

But my "normal" workaround (do @Bean(name="testFoo") won't work here because the @Named injector in the consuming classes will now fail.

Are there any solutions?

1 Answers

I managed to do it, but it was quite painful. I had to use a BeanDefinitionRegistryPostProcessor. Setting up GenericBeanDefinition is ugly as sin. It wasn't too bad here, because the mocks are no-args. If you have to base them on injected or set constructor args, it becomes quite involved indeed.

/**
 * This class basically removes the existing bean definition and substitutes in the mocks.
 * Normally we don't need this - we just change the bean name. However because the
 * classes use an @Named qualifier, more heroic efforts are needed
 */
public static class OverridePostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(final BeanDefinitionRegistry registry) throws BeansException {
        if (registry.isBeanNameInUse("vault-uri-supplier")) {
            registry.removeBeanDefinition("vault-uri-supplier");
        }
        // Note: These are a bear to work with, but in theory you can build
        // them from scratch based on injected beans etc. Fortunately these
        // two examples just needed a no-args supplier
        GenericBeanDefinition g = new GenericBeanDefinition();
        g.setBeanClass(URISupplier.class);
        g.setInstanceSupplier(this::vaultURISupplier);
        registry.registerBeanDefinition("vault-uri-supplier", g);
        if (registry.isBeanNameInUse("taskRequestValidator")) {
            registry.removeBeanDefinition("taskRequestValidator");
        }
        g = new GenericBeanDefinition();
        g.setBeanClass(TaskRequestValidator.class);
        g.setInstanceSupplier(this::taskRequestValidator);
        registry.registerBeanDefinition("taskRequestValidator", g);


    }

    URISupplier vaultURISupplier() {
        return new com.opentable.credentials.client.internal.TestVaultConfiguration.MockSupplier();
    }
    TaskRequestValidator taskRequestValidator() {
        return (getTokenRequest, servicePolicy) -> ValidationResult.OK;
    }

    @Override
    public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) throws BeansException {
        /* No op */
    }
}
Related