【问题标题】:Why is my kafka listener not working in my Unit Test为什么我的 kafka 监听器在我的单元测试中不起作用
【发布时间】:2018-02-24 23:48:36
【问题描述】:

问题

我正在尝试对发送整数的 kafka 生产者进行单元测试。运行我的单元测试时,我可以看到输出表明我的生产者和消费者在控制台中正常工作。也就是说,生产者发送一个零值,消费者收到零,并将该整数与运行总数相加。

sending data = 0
received content = 0
sending total = 0

但是,当测试 ConsumerRecord 返回一个空条目时,单元测试最终会失败。我猜听者根本不工作。

java.lang.AssertionError: 
Expected: a ConsumerRecord with value 0
     but: is null

问题

我定义容器/消息监听器的方式是否有错误?或者我的单元测试有更根本的错误?

这个 url 包含我的生产者/配置和消费者/配置的代码。在那里查看它可能会更容易,而不是在这里使代码部分变得很大。

https://github.com/ewingian/RestCalculator/tree/master/src/main/java/com/calculator/kafka

单元测试

package com.calculator;

/**
 * Created by ian on 2/9/18.
 */
import com.calculator.kafka.services.KafkaProducer;
import com.calculator.kafka.services.KafkaConsumer;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import org.springframework.kafka.listener.KafkaMessageListenerContainer;
import org.springframework.kafka.listener.MessageListener;
import org.springframework.kafka.listener.config.ContainerProperties;
import org.springframework.kafka.test.rule.KafkaEmbedded;
import org.springframework.kafka.test.utils.ContainerTestUtils;
import org.springframework.kafka.test.utils.KafkaTestUtils;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.assertThat;
import static org.springframework.kafka.test.assertj.KafkaConditions.key;
import static org.springframework.kafka.test.hamcrest.KafkaMatchers.*;

@RunWith(SpringRunner.class)
@SpringBootTest
@DirtiesContext
public class KafkaTest {

    // in case I need to send some integers
    private Integer i1 = 0;
    private Integer i2 = 3;

    private static final String SENDER_TOPIC = "input";

    private List<Integer> l1;

    @Autowired
    private KafkaProducer producer;

    @Autowired
    private KafkaConsumer consumer;

    private KafkaMessageListenerContainer<String, Integer> container;

    private BlockingQueue<ConsumerRecord<String, Integer>> records;

    private static final Logger LOGGER = LoggerFactory.getLogger(KafkaTest.class);


    @ClassRule
    public static KafkaEmbedded embeddedKafka = new KafkaEmbedded(1, true, SENDER_TOPIC);



    @Before
    public void testTemplate() throws Exception {

        // set up the Kafka consumer properties
        Map<String, Object> consumerProperties = KafkaTestUtils.consumerProps("test-group", "false", embeddedKafka);

        // create a Kafka consumer factory
        DefaultKafkaConsumerFactory<String, Integer> consumerFactory = new DefaultKafkaConsumerFactory<>(consumerProperties);

        // set the topic that needs to be consumed
        ContainerProperties containerProperties = new ContainerProperties(SENDER_TOPIC);

        // create a Kafka MessageListenerContainer
        container = new KafkaMessageListenerContainer<>(consumerFactory, containerProperties);

        // create a thread safe queue to store the received message
        records = new LinkedBlockingQueue<>();

        // setup a Kafka message listener
        container.setupMessageListener(new MessageListener<String, Integer>() {
            @Override
            public void onMessage(ConsumerRecord<String, Integer> record) {
                LOGGER.debug("test-listener received message='{}'", record.toString());
                records.add(record);
            }
        });

        // start the container and underlying message listener
        container.start();

        // wait until the container has the required number of assigned partitions
        ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic());


    }

    @After
    public void tearDown() {
        // stop the container
        container.stop();
    }

    @Test
    public void testSend() throws InterruptedException {
        // send the message
        producer.send(i1);

        // check that the message was received
        ConsumerRecord<String, Integer> received = records.poll(10, TimeUnit.SECONDS);
        // Hamcrest Matchers to check the value
        assertThat(received, hasValue(i1));

        // AssertJ Condition to check the key
        assertThat(received, hasKey(null));
    }
}

【问题讨论】:

    标签: unit-testing spring-kafka


    【解决方案1】:

    您需要使用正确的键/值序列化器/反序列化器。分别为Integer/String设置的KTU,需要String/Integer。

    您的测试用例的这个修改版本有效...

    @RunWith(SpringRunner.class)
    @SpringBootTest
    @DirtiesContext
    public class KafkaTest {
    
        // in case I need to send some integers
        private final Integer i1 = 0;
    
        private final Integer i2 = 3;
    
        private static final String SENDER_TOPIC = "input";
    
        private List<Integer> l1;
    
        private KafkaMessageListenerContainer<String, Integer> container;
    
        private BlockingQueue<ConsumerRecord<String, Integer>> records;
    
        private static final Logger LOGGER = LoggerFactory.getLogger(KafkaTest.class);
    
        @ClassRule
        public static KafkaEmbedded embeddedKafka = new KafkaEmbedded(1, true, SENDER_TOPIC);
    
        @Before
        public void testTemplate() throws Exception {
    
            // set up the Kafka consumer properties
            Map<String, Object> consumerProperties = KafkaTestUtils.consumerProps("test-group", "false", embeddedKafka);
            consumerProperties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
            consumerProperties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, IntegerDeserializer.class);
    
            // create a Kafka consumer factory
            DefaultKafkaConsumerFactory<String, Integer> consumerFactory = new DefaultKafkaConsumerFactory<>(
                    consumerProperties);
    
            // set the topic that needs to be consumed
            ContainerProperties containerProperties = new ContainerProperties(SENDER_TOPIC);
    
            // create a Kafka MessageListenerContainer
            container = new KafkaMessageListenerContainer<>(consumerFactory, containerProperties);
    
            // create a thread safe queue to store the received message
            records = new LinkedBlockingQueue<>();
    
            // setup a Kafka message listener
            container.setupMessageListener(new MessageListener<String, Integer>() {
                @Override
                public void onMessage(ConsumerRecord<String, Integer> record) {
                    LOGGER.debug("test-listener received message='{}'", record.toString());
                    records.add(record);
                }
            });
    
            // start the container and underlying message listener
            container.start();
    
            // wait until the container has the required number of assigned partitions
            ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic());
    
        }
    
        @After
        public void tearDown() {
            // stop the container
            container.stop();
        }
    
        @Test
        public void testSend() throws InterruptedException {
            // send the message
            Map<String, Object> producerProps = KafkaTestUtils.producerProps(embeddedKafka);
            producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
            producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, IntegerSerializer.class);
            ProducerFactory<String, Integer> pf = new DefaultKafkaProducerFactory<>(producerProps);
            KafkaTemplate<String, Integer> template = new KafkaTemplate<>(pf);
            template.send(SENDER_TOPIC, i1);
    
            // check that the message was received
            ConsumerRecord<String, Integer> received = records.poll(10, TimeUnit.SECONDS);
            // Hamcrest Matchers to check the value
            assertThat(received, hasValue(i1));
    
            // AssertJ Condition to check the key
            assertThat(received, hasKey(null));
    
            System.out.println(received);
        }
    
    }
    

    编辑

    回应您在下面的评论...

    您的生产者无法与嵌入式(测试)代理交谈。这对我来说很好:

    @Configuration
    public class KafkaProducerConfig {
    
        @Value("${" + KafkaEmbedded.SPRING_EMBEDDED_KAFKA_BROKERS + ":localhost:9092}")
        private String bootstrapServer;
    
        @Bean
        public ProducerFactory<String, Integer> producerFactory() {
            Map<String, Object> configProps = new HashMap<>();
            configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServer);
            configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
            configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, IntegerSerializer.class);
            return new DefaultKafkaProducerFactory<>(configProps);
        }
    
        @Bean
        public KafkaTemplate<String, Integer> kafkaTemplate() {
            return new KafkaTemplate<>(producerFactory());
        }
    
    }
    

    @RunWith(SpringRunner.class)
    @SpringBootTest
    @DirtiesContext
    public class KafkaTest {
    
        // in case I need to send some integers
        private final Integer i1 = 42;
    
        private final Integer i2 = 3;
    
        private static final String SENDER_TOPIC = "input";
    
        private List<Integer> l1;
    
        @Autowired
        private KafkaProducer producer;
    
        private KafkaMessageListenerContainer<String, Integer> container;
    
        private BlockingQueue<ConsumerRecord<String, Integer>> records;
    
        private static final Logger LOGGER = LoggerFactory.getLogger(KafkaTest.class);
    
        @ClassRule
        public static KafkaEmbedded embeddedKafka = new KafkaEmbedded(1, true, SENDER_TOPIC);
    
        @Before
        public void testTemplate() throws Exception {
    
            // set up the Kafka consumer properties
            Map<String, Object> consumerProperties = KafkaTestUtils.consumerProps("test-group", "false", embeddedKafka);
            consumerProperties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
            consumerProperties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, IntegerDeserializer.class);
    
            // create a Kafka consumer factory
            DefaultKafkaConsumerFactory<String, Integer> consumerFactory = new DefaultKafkaConsumerFactory<>(
                    consumerProperties);
    
            // set the topic that needs to be consumed
            ContainerProperties containerProperties = new ContainerProperties(SENDER_TOPIC);
    
            // create a Kafka MessageListenerContainer
            container = new KafkaMessageListenerContainer<>(consumerFactory, containerProperties);
    
            // create a thread safe queue to store the received message
            records = new LinkedBlockingQueue<>();
    
            // setup a Kafka message listener
            container.setupMessageListener(new MessageListener<String, Integer>() {
                @Override
                public void onMessage(ConsumerRecord<String, Integer> record) {
                    LOGGER.debug("test-listener received message='{}'", record.toString());
                    records.add(record);
                }
            });
    
            // start the container and underlying message listener
            container.start();
    
            // wait until the container has the required number of assigned partitions
            ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic());
    
        }
    
        @After
        public void tearDown() {
            // stop the container
            container.stop();
        }
    
        @Test
        public void testSend() throws InterruptedException {
            // send the message
            producer.send(i1);
    
            // check that the message was received
            ConsumerRecord<String, Integer> received = records.poll(10, TimeUnit.SECONDS);
            // Hamcrest Matchers to check the value
            assertThat(received, hasValue(i1));
    
            // AssertJ Condition to check the key
            assertThat(received, hasKey(null));
    
            System.out.println(received);
        }
    
    }
    

    ConsumerRecord(topic = input, partition = 1, offset = 0, CreateTime = 1519487134283, checksum = 866641474, serialized key size = -1, serialized value size = 4, key = null, value = 42)
    

    【讨论】:

    • 谢谢这是一个开始,但是对于我写的制作人来说仍然不起作用。这是测试的最终目标,尝试将我的生产者集成到其中。我使用的生产者:configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); 我尝试将我的 Consumer 属性与之匹配,但仍然没有运气。
    • 查看我的答案的编辑 - 您的制作人已连接到与其他经纪人交谈。
    • 给生产者接线解决了我的问题。奇怪的是,当我更新我的 Producer 属性时,我得到了一个 org.apache.kafka.common.errors.SerializationException: Error deserializing key/value for partition topic-1 at offset 0,无论我尝试运行什么项目,它似乎都会在我的 kafka 服务器中传播,即使在停止并再次启动服务器之后也是如此。我确保生产者序列化程序匹配,重置我的机器并且测试运行良好。谢谢
    • @GaryRussell 我尝试遵循您的方法,并且在大多数情况下都可以正常工作。但我相信我的代码中存在一些与时间相关的问题。 // check that the message was received ConsumerRecord&lt;String, Integer&gt; received = records.poll(10, TimeUnit.SECONDS); 这个received 有时会变为空(50% 的情况)。如果我尝试调试并将调试指针放在那里,那么它就可以工作。你知道我怎样才能找到问题吗?我试图玩等待超时但没有运气!谢谢
    • 不要在 cmets 中提出新问题;而是问一个新问题。这可能是一场比赛。尝试将ConsumerConfig.AUTO_OFFSET_RESET_CONFIG 设置为earliest。 KTU 现在默认将此设置为 true(从 2.5 开始)。如果这没有帮助,请提出一个新问题,显示您的完整测试用例。
    猜你喜欢
    • 2019-11-26
    • 2014-06-11
    • 1970-01-01
    • 2019-07-13
    • 1970-01-01
    • 2018-07-12
    • 1970-01-01
    • 1970-01-01
    • 2012-03-19
    相关资源
    最近更新 更多