问题描述
编写JUnit测试以测试控制器方法,并使用Mockito模拟服务方法。
服务等级
@Service
public record ProductProducer(ReplyingKafkaTemplate<String,Object,Object> _replyTemplate,ObjectMapper mapper) implements IProductProducer {}
kafka配置
@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> replyer(ProducerFactory<String,Object> pf,ConcurrentKafkaListenerContainerFactory<String,Object> containerFactory) {
containerFactory.setReplyTemplate(kafkaTemplate(pf));
ConcurrentMessageListenerContainer<String,Object> container = replyContainer(containerFactory);
ReplyingKafkaTemplate<String,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测试
@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)));
}
}
}
我的应用失败,错误为
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,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
现在将哪个Bean作为主要对象。
解决方法
您为什么有四个集装箱工厂?通常,您只需要一个。
将参数名称更改为您想要用于回复容器的名称;例如kafkaListenerContainerFactory
。
@Bean
public ReplyingKafkaTemplate<String,Object,Object> replyer(ProducerFactory<String,Object> pf,ConcurrentKafkaListenerContainerFactory<String,Object> kafkaListenerContainerFactory) {