1

Writing the JUnit test to test the controller method, mocking the service methods using Mockito.

service class

@Service
public record ProductProducer(ReplyingKafkaTemplate<String, Object, Object> _replyTemplate,
                              ObjectMapper mapper) implements IProductProducer {}

kafka configuration

@Configuration
public class KafkaConfiguration {
    private ApplicationYmlConfiguration bootstrapAddress;

    public KafkaConfiguration(ApplicationYmlConfiguration bootstrapAddress) {
        this.bootstrapAddress = bootstrapAddress;
    }

    //region Kafka configuration
    @Bean
    public Map<String, Object> consumerConfigs() {
        Map<String, Object> props = new HashMap<>();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, this.bootstrapAddress.getKafka().getBootstrapAddress());
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        return props;
    }

    @Bean
    public ReplyingKafkaTemplate<String, Object, Object> replyer(ProducerFactory<String, Object> pf,
                                                                             ConcurrentKafkaListenerContainerFactory<String, Object> containerFactory) {

        containerFactory.setReplyTemplate(kafkaTemplate(pf));
        ConcurrentMessageListenerContainer<String, Object> container = replyContainer(containerFactory);
        ReplyingKafkaTemplate<String, Object, Object> replyer = new ReplyingKafkaTemplate<>(pf, container);
        return replyer;
    }

    @Bean
    public ConcurrentMessageListenerContainer<String, Object> replyContainer(
            ConcurrentKafkaListenerContainerFactory<String, Object> containerFactory) {

        ConcurrentMessageListenerContainer<String, Object> container =
                containerFactory.createContainer(ProductTopicConstants.LISTNER_CONTAINER);
        container.getContainerProperties().setGroupId(ProductTopicConstants.LISTNER_CONTAINER);
        container.setBatchErrorHandler(new BatchLoggingErrorHandler());
        return container;
    }

    @Bean
    public KafkaTemplate<String, Object> kafkaTemplate(ProducerFactory<String, Object> pf) {
        return new KafkaTemplate<>(pf);
    }
    //endregion

Junit test

@Category("Unit Testing")
@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
class ProductController {
    @MockBean
    ProductProducer productService;

    @Autowired
    private MockMvc mockMvc;

    @Nested
    @DisplayName("Find")
    class FindMethod {

        @Test
        @DisplayName("Should return product based on the specified Id")
        void shouldReturnProductBasedOnTheSpecifiedId() throws Exception {
            String Id = java.util.UUID.randomUUID().toString();
            ProductViewModel productViewModel = new ProductViewModel(Id, "Product 1", 100, "Product 1 description", 0);
            doReturn(productViewModel).when(productService).findById(Id);
            mockMvc.perform(get(String.format("/product/%s"), Id))

                    //Validate the response code and content type
                    .andExpect(status().isOk())
                    .andExpect((ResultMatcher) content().contentType(MediaType.APPLICATION_JSON_VALUE))

                    //validate the headers
                    .andExpect(header().string(HttpHeaders.ETAG, String.format("\"%s\"", Id)))
                    .andExpect(header().string(HttpHeaders.LOCATION, String.format("/product/%s", Id)))

                    //Validate the return filed
                    .andExpect((ResultMatcher) jsonPath("$.id", is(Id)))
                    .andExpect((ResultMatcher) jsonPath("$.name", is("Product 1")))
                    .andExpect((ResultMatcher) jsonPath("$.price", is(100)))
                    .andExpect((ResultMatcher) jsonPath("$.description", is("Product 1 description")))
                    .andExpect((ResultMatcher) jsonPath("$.version", is(0)));

        }
    }
}

My application failed with error as

Parameter 1 of method replyer in fete.bird.fetebirdproduct.configuration.kafka.KafkaConfiguration required a single bean, but 4 were found:
    - getAllProductsContainerFactory: defined by method 'getAllProductsContainerFactory' in class path resource [fete/bird/fetebirdproduct/configuration/kafka/KafkaConfiguration.class]
    - getDeleteProductContainerFactory: defined by method 'getDeleteProductContainerFactory' in class path resource [fete/bird/fetebirdproduct/configuration/kafka/KafkaConfiguration.class]
    - addUpdateProductContainerFactory: defined by method 'addUpdateProductContainerFactory' in class path resource [fete/bird/fetebirdproduct/configuration/kafka/KafkaConfiguration.class]
    - kafkaListenerContainerFactory: defined by method 'kafkaListenerContainerFactory' in class path resource [org/springframework/boot/autoconfigure/kafka/KafkaAnnotationDrivenConfiguration.class]


Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

2020-08-28 16:05:31.137 ERROR 2408 --- [           main] o.s.test.context.TestContextManager      : Caught exception while allowing TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener@7a1234bf] to prepare test instance [fete.bird.fetebirdproduct.unit.ProductController@4f63909f]

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'replyer' defined in class path resource [fete/bird/fetebirdproduct/configuration/kafka/KafkaConfiguration.class]: Unsatisfied dependency expressed through method 'replyer' parameter 1; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory<java.lang.String, java.lang.Object>' available: expected single matching bean but found 4: getAllProductsContainerFactory,getDeleteProductContainerFactory,addUpdateProductContainerFactory,kafkaListenerContainerFactory
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:797) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:538) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1336) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1176) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:556) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:897) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879) ~[spring-context-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551) ~[spring-context-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758) ~[spring-boot-2.3.3.RELEASE.jar:2.3.3.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750) ~[spring-boot-2.3.3.RELEASE.jar:2.3.3.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.3.3.RELEASE.jar:2.3.3.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-2.3.3.RELEASE.jar:2.3.3.RELEASE]
    at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:120) ~[spring-boot-test-2.3.3.RELEASE.jar:2.3.3.RELEASE]
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99) ~[spring-test-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124) ~[spring-test-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    ... 77 common frames omitted
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory<java.lang.String, java.lang.Object>' available: expected single matching bean but found 4: getAllProductsContainerFactory,getDeleteProductContainerFactory,addUpdateProductContainerFactory,kafkaListenerContainerFactory
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:220) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1285) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1227) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:884) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:788) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    ... 96 common frames omitted

Now which Bean to make primary.

San Jaisy
  • 15,327
  • 34
  • 171
  • 290

1 Answers1

0

Why do you have four container factories? You typically only need one.

Change the parameter name to the one you want to use for the reply container; e.g. kafkaListenerContainerFactory.

@Bean
public ReplyingKafkaTemplate<String, Object, Object> replyer(ProducerFactory<String, Object> pf,
                                                                         ConcurrentKafkaListenerContainerFactory<String, Object> kafkaListenerContainerFactory) {
Gary Russell
  • 166,535
  • 14
  • 146
  • 179