RabbitListenerTestHarness вставляет фактические объекты в слушателей

Сценарий: Junit для микросервиса, который прослушивает очередь и отправляет на обмен в rabbitMQ после извлечения данных.

Проблема:

RabbitListenerTestHarness is creating mock object for the Rabbit Listener class alone, Actual objects are being instantiated for Listeners Autowired components

Я не мог найти способ вручную ввести фиктивные бины в слушателя. Это заставляет Junit отправлять тестовые сообщения в фактические очереди, настроенные в микросервисе во время выполнения Junit.

Обходной путь: Единственный способ, которым я мог бы использовать проект rabbit-test, - это настроить тестовый обмен для отправки сообщений во время выполнения Junit.

Запрос: Я хотел понять, есть ли способ лучше написать Junit для Rabbit Listener. Также я хотел понять, есть ли способ вручную вводить фиктивные объекты в компоненты с автоматическим подключением Rabbit Listeners.

Пример фрагмента кода:

Класс слушателя кролика

@RabbitListener(id = "id", bindings = @QueueBinding(value = @Queue(value = "sampleQueue", durable = "true", autoDelete = "false"),key = "sampleRoutingKey", exchange = @Exchange(value = "sampleExchange", durable = "true", ignoreDeclarationExceptions = "true", type = EXCHANGE_TYPE)))
public void getMessageFromQueue(@Payload EventModel event) throws ListenerExecutionFailedException, JAXBException {
    dataExporterService.exportDataAndPostToRabbit(event);
}

Класс обслуживания

@Autowired
DataExtractorRepository dataExtractorRepository;
@Autowired
DataPublihserRepository dataPublisherRepo;
public void exportDataAndPostToRabbit(EventModel event) throws JAXBException {
        dataPublisherRepo.sendMessageToExchange(dataExtractorRepository.extractOrderData(event), exchangeName, routingKeyValue);
}

DataPublihserRepository имеет внутреннее Autowired rabbitTemplate. DataExtractorRepository подключается к БД внутренне для получения сообщения.

Тестовый класс

@Autowired
private RabbitListenerTestHarness harness;

@Autowired
private RabbitTemplate rabbitTemplate;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
DataExporterController = this.harness.getSpy("id");
}

@Test
public void shouldReceiveMessage() throws Exception {
LatchCountDownAndCallRealMethodAnswer answer = new LatchCountDownAndCallRealMethodAnswer(1);
doAnswer(answer).when(DataExporterController).getMessageFromQueue(any(EventModel.class));
rabbitTemplate.convertAndSend("sampleExchange", "sampleRoutingKey", createMessage());
assertTrue(answer.getLatch().await(10, TimeUnit.SECONDS));
verify(DataExporterController, times(1)).getMessageFromQueue(any(OrderEventsModel.class));
verify(orderDataExporterController, times(1)).getMessageFromQueue(any(OrderEventsModel.class));
}

 private Message createMessage() {
        String inputObject = "{\"id\":12345}";
        MessageProperties props = MessagePropertiesBuilder.newInstance().setContentType(MessageProperties.CONTENT_TYPE_JSON).build();
        return new Message(inputObject.getBytes(), props);
}
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
0
944
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Жгут предназначен как механизм для проверки того, что слушатель получил данные в интеграционном тесте. Чтобы выполнить модульное тестирование слушателя, вызовите его метод onMessage.

Например, используя Mockito, учитывая

public class MyListener {

    @Autowired
    private SomeService service;

    @RabbitListener(id = "myListener", queues = "foo")
    public void listen(Foo foo) {
        this.service.process(foo);
    }

}

а также

public interface SomeService {

    void process(Foo foo);

}

тогда

@RunWith(SpringRunner.class)
public class So53136882ApplicationTests {

    @Autowired
    private RabbitListenerEndpointRegistry registry;

    @Autowired
    private SomeService service;

    @Test
    public void test() throws Exception {
        SimpleMessageListenerContainer container = (SimpleMessageListenerContainer) this.registry
                .getListenerContainer("myListener");
        ChannelAwareMessageListener listener = (ChannelAwareMessageListener) container.getMessageListener();
        Message message = MessageBuilder.withBody("{\"bar\":\"baz\"}".getBytes())
                .andProperties(MessagePropertiesBuilder.newInstance()
                        .setContentType("application/json")
                        .build())
                .build();
        listener.onMessage(message, mock(Channel.class));
        verify(this.service).process(new Foo("baz"));
    }

    @Configuration
    @EnableRabbit
    public static class config {

        @Bean
        public ConnectionFactory mockCf() {
            return mock(ConnectionFactory.class);
        }

        @Bean
        public MessageConverter converter() {
            return new Jackson2JsonMessageConverter();
        }

        @Bean
        public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
            SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
            factory.setConnectionFactory(mockCf());
            factory.setMessageConverter(converter());
            factory.setAutoStartup(false);
            return factory;
        }

        @Bean
        public MyListener myListener() {
            return new MyListener();
        }

        @Bean
        public SomeService service() {
            return mock(SomeService.class);
        }

    }

}

Обратите внимание, что фабрика контейнеров не запускает контейнер слушателя.

Для тестовой публикации вставьте макет RabbitOperations, который реализуется RabbitTemplate.

Например, учитывая

public class SomeServiceImpl implements SomeService {

    @Autowired
    private RabbitOperations rabbitOperations;

    @Override
    public void process(Foo foo) {
        this.rabbitOperations.convertAndSend(
                "someExchange", "someRoutingKey", new Foo(foo.getBar().toUpperCase()));
    }

}

а также

@Bean
public SomeService service() {
    return new SomeServiceImpl();
}

@Bean
public RabbitOperations rabbitTemplate() {
    return mock(RabbitOperations.class);
}

тогда

@Test
public void test() throws Exception {
    SimpleMessageListenerContainer container = (SimpleMessageListenerContainer) this.registry
            .getListenerContainer("myListener");
    ChannelAwareMessageListener listener = (ChannelAwareMessageListener) container.getMessageListener();
    Message message = MessageBuilder.withBody("{\"bar\":\"baz\"}".getBytes())
            .andProperties(MessagePropertiesBuilder.newInstance()
                    .setContentType("application/json")
                    .build())
            .build();
    listener.onMessage(message, mock(Channel.class));
    verify(this.rabbitTemplate).convertAndSend("someExchange", "someRoutingKey", new Foo("BAZ"));
}

Привет @Gary! Прежде всего, большое спасибо за подробное понимание того, как реализовать модульные тесты для Rabbit Listener. Мне это действительно очень помогло. Также я пытался ввести adviceChain и обработчик ошибок в компонент SimpleRabbitListenerContainerFactory в классе конфигурации, но эти классы, похоже, не вызываются. Требуется ли для этого какая-то конфигурация?

Jayadeep23 05.11.2018 14:51

Они являются свойствами контейнера слушателя, и, поскольку мы вызываем слушателя напрямую (а не через контейнер), они пропускаются. является позволяет имитировать клиента rabbitmq и вводить сообщения на более низком уровне в стеке, но это довольно сложно. См. этот тестовый пример для примера.

Gary Russell 05.11.2018 15:26

Большое тебе спасибо. Конечно, я изучу это.

Jayadeep23 06.11.2018 10:55

@ Jayadeep23, вы также можете использовать кроликmq-mock, чтобы не беспокоиться о насмешках, и такие вещи, как обработчик ошибок, будут вызываться как в производственном режиме

Loïc Le Doyen 10.04.2019 08:32

Другие вопросы по теме