SpringBoot 应用中的@RabbitListener 方法测试

Posted

技术标签:

【中文标题】SpringBoot 应用中的@RabbitListener 方法测试【英文标题】:@RabbitListener method testing in SpringBoot app 【发布时间】:2016-03-03 20:00:00 【问题描述】:

代码:

RabbitMQ 监听器:

@Component
public class ServerThroughRabbitMQ implements ServerThroughAMQPBroker 
    private static final AtomicLong ID_COUNTER=new AtomicLong();
    private final long instanceId=ID_COUNTER.incrementAndGet();


    @Autowired
    public ServerThroughRabbitMQ( UserService userService,LoginService loginService....)
....
    

    @Override
    @RabbitListener(queues = "#registerQueue.name")
    public String registerUserAndLogin(String json) 
       .....
    

服务器配置:

@Configuration
public class ServerConfig 
    @Value("$amqp.broker.exchange-name")
    private String exchangeName;
    @Value("$amqp.broker.host")
    private String ampqBrokerHost;
    @Value("$amqp.broker.quidco.queue.postfix")
    private String quidcoQueuePostfix;
    @Value("$amqp.broker.quidco.queue.durability:true")
    private boolean quidcoQueueDurability;
    @Value("$amqp.broker.quidco.queue.autodelete:false")
    private boolean quidcoQueueAutodelete;

    private String registerAndLoginQuequName;


    @PostConstruct
    public void init() 
        registerAndLoginQuequName = REGISTER_AND_LOGIN_ROUTING_KEY + quidcoQueuePostfix;
    public String getRegisterAndLoginQueueName() 
        return registerAndLoginQuequName;
    

    public String getLoginAndCheckBonusQueueName() 
        return loginAndCheckBonusQuequName;
    



    @Bean
    public ConnectionFactory connectionFactory() 
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory(ampqBrokerHost);
        return connectionFactory;
    

    @Bean
    public AmqpAdmin amqpAdmin() 
        return new RabbitAdmin(connectionFactory());
    

    @Bean
    public TopicExchange topic() 
        return new TopicExchange(exchangeName);
    

    @Bean(name = "registerQueue")
    public Queue registerQueue() 
        return new Queue(registerAndLoginQuequName, quidcoQueueDurability, false, quidcoQueueAutodelete);
    


    @Bean
    public Binding bindingRegisterAndLogin() 
        return BindingBuilder.bind(registerQueue()).to(topic()).with(REGISTER_AND_LOGIN_ROUTING_KEY);
    

   

测试配置:

@EnableRabbit
@TestPropertySource("classpath:test.properties")
public class ServerThroughAMQPBrokerRabbitMQIntegrationTestConfig 
    private final ExecutorService=Executors.newCachedThreadPool();
    private LoginService loginServiceMock=mock(LoginService.class);
    private UserService userServiceMock =mock(UserService.class);

    @Bean
    public ExecutorService executor() 
        return executorService;
    

    @Bean
    public LoginService getLoginServiceMock() 
        return loginServiceMock;
    

    @Bean
    public UserService getUserService() 
        return userServiceMock;
    

    @Bean
    @Autowired
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) 
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMaxConcurrentConsumers(5);
        return factory;
    

    @Bean
    @Autowired
    public RabbitTemplate getRabbitTemplate(ConnectionFactory connectionFactory) 
        final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    

    @Bean
    public ServerThroughRabbitMQ getServerThroughRabbitMQ() 
        return new ServerThroughRabbitMQ(userServiceMock, loginServiceMock,...);
    


集成测试:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes =ServerConfig.class,ServerThroughAMQPBrokerRabbitMQIntegrationTestConfig.class)
@Category(IntegrationTest.class)
@TestPropertySource("classpath:test.properties")
public class ServerThroughAMQPBrokerRabbitMQIntegrationTest 
    final private ObjectMapper jackson = new ObjectMapper();
    @Autowired
    private ExecutorService executor;

    @Autowired
    private ServerThroughRabbitMQ serverThroughRabbitMQ;

    @Autowired
    private RabbitTemplate template;

    @Autowired
    private TopicExchange exchange;

    @Autowired
    UserService userService;

    @Autowired
    LoginService loginService;

    @Autowired
    private AmqpAdmin amqpAdmin;

    @Autowired
    private ServerConfig serverConfig;

    final String username = "username";
    final String email = "email@email.com";
    final Integer tcVersion=1;
    final int quidcoUserId = 1;
    final String jwt = ProcessLauncherForJwtphpBuilderUnitWithCxtTest.EXPECTED_JWT;


    @Before
    public void cleanAfterOthersForMyself() 
        cleanTestQueues();
    

    @After
    public void cleanAfterMyselfForOthers() 
        cleanTestQueues();
    

    private void cleanTestQueues() 
        amqpAdmin.purgeQueue(serverConfig.getRegisterAndLoginQueueName(), false);
    

    @Test
    @Category(SlowTest.class,IntegrationTest.class)
    public void testRegistrationAndLogin() throws TimeoutException 
        final Waiter waiter = new Waiter();

        when(userService.register(anyString(), anyString(), anyString())).thenReturn(...);
        when(loginService....()).thenReturn(...);


        executor.submit(() -> 
            final RegistrationRequest request = new RegistrationRequest(username, email,tcVersion);
            final String response;
            try 
                //@todo: converter to convert RegistrationRequest inside next method to json
                response = (String) template.convertSendAndReceive(exchange.getName(), REGISTER_AND_LOGIN_ROUTING_KEY.toString(), jackson.writeValueAsString(request));
                waiter.assertThat(response, not(isEmptyString()));

                final RegistrationResponse registrationResponse = jackson.readValue(response, RegistrationResponse.class);
                waiter.assertThat(...);
                waiter.assertThat(...);

             catch (Exception e) 
                throw new RuntimeException(e);
            
            waiter.resume();
        );

        waiter.await(5, TimeUnit.SECONDS);
    


当我单独运行该测试时,一切正常,但是当我与其他测试一起运行它时,没有使用模拟的 ServerThroughRabbitMQ,因此一些弹簧缓存强制使用旧的兔子侦听器。

我尝试调试它,我可以看到,正确的 bean 正在自动连接到测试,但由于某种原因,旧的侦听器正在使用(旧 bean 字段 instanceId=1 新的模拟 bean instanceId=3)并且测试失败(不是确定它是怎么可能的,所以如果在现有的旧 bean 的情况下,我假设会得到一个自动装配异常)。

我尝试在 BEFORE_CLASS 之前使用 @DirtiesContext,但遇到了另一个问题(请参阅 here)

【问题讨论】:

你有机会通过 git 分享你的项目问题部分吗(通过额外的实验来检查这个测试真的很有用)? 您找到满意的解决方案了吗? 【参考方案1】:

RabbitMQ 和集成测试可能很难,因为 Rabbit MQ 保持某种状态: - 来自队列中先前测试的消息 - 来自先前测试的侦听器仍在侦听队列

有几种方法:

在开始测试之前清除所有队列(这可能就是您所说的cleanTestQueues()) 删除所有队列(或使用临时队列)并在每次测试前重新创建它们 使用 Rabbit Admin Rest API 杀死侦听器或先前测试的连接 删除虚拟主机并为每个测试重新创建基础设施(这是最残酷的方式)

【讨论】:

以上是关于SpringBoot 应用中的@RabbitListener 方法测试的主要内容,如果未能解决你的问题,请参考以下文章

无法将存储的 Vault 机密检索到 Kubernetes 中的 Springboot 应用程序

Validated校验在springboot框架中的应用(教程版)

查看SpringBoot应用中的嵌入式tomcat的版本

[转] SpringBoot RESTful 应用中的异常处理小结

SpringBoot SpringBoot中的缓存机制

SpringBoot 无法从多模块 Java 应用程序中的另一个模块识别 RestController