使用自定义反序列化的Spring-kafka-test测试JSON消息

时间:2019-06-03 10:43:33

标签: java apache-kafka spring-kafka spring-kafka-test

我基本上已经使用EmbeddedKafka成功地建立了一个测试,该测试产生并使用一条消息。以下是测试的工作版本:

@RunWith(SpringRunner.class)
@SpringBootTest
@EmbeddedKafka
public class KafkaFlowTest {

    @Autowired
    EmbeddedKafkaBroker broker;

    public static final String EMAIL_TOPIC = "email-service";

    static {
        System.setProperty(EmbeddedKafkaBroker.BROKER_LIST_PROPERTY, "spring.kafka.bootstrap-servers");
    }

    private Consumer<String, KafkaEmailMessageWrapper> consumer;
    private Producer<String, KafkaEmailMessageWrapper> producer;

    @Before
    public void setUp() {
        Map<String, Object> consumerProps = KafkaTestUtils.consumerProps("group", "true", broker);
        consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
        consumerProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
        consumerProps.put(JsonDeserializer.TRUSTED_PACKAGES, "*");
        Map<String, Object> producerProps = KafkaTestUtils.producerProps(broker);
        producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);

        DefaultKafkaConsumerFactory<String, KafkaEmailMessageWrapper> cf =
                new DefaultKafkaConsumerFactory<>(
                        consumerProps,
                        new StringDeserializer(),
                        new JsonDeserializer<>(KafkaEmailMessageWrapper.class)
                );
        consumer = cf.createConsumer();
        consumer.subscribe(Collections.singleton(EMAIL_TOPIC));
        consumer.poll(Duration.ZERO);

        DefaultKafkaProducerFactory<String, KafkaEmailMessageWrapper> pf =
                new DefaultKafkaProducerFactory<>(producerProps);
        producer = pf.createProducer();
    }

    @Test
    public void testNoErrorMessageFlow() {
        KafkaEmailMessageWrapper wrapperMessage = KafkaEmailMessageWrapper.builder()
                .emailRequest(
                    EmailMessage.builder()
                        .body("body")
                        .from("me@me.com")
                        .to(new String[]{"you@you.com"})
                        .subject("hey!")
                        .build()
                )
                .build();

        producer.send(new ProducerRecord<>(EMAIL_TOPIC, "whatever", wrapperMessage));
        producer.flush();

        ConsumerRecord<String, KafkaEmailMessageWrapper> record = KafkaTestUtils.getSingleRecord(consumer, EMAIL_TOPIC);
        KafkaEmailMessageWrapper receivedMessage = record.value();
        assertEquals(wrapperMessage.getEmailRequest().getSubject(), receivedMessage.getEmailRequest().getSubject());
    }

}

这很好用,除非我在to字段上添加assertEquals,该字段在JSON中是由;分隔的电子邮件地址列表,并且在该类中是字符串数组。因此,在EmailMessage类中,我在to字段中标注了@JsonDeserializer,指向一个自定义反序列化器,该解串器沿;进行拆分,依此类推。当应用程序运行时,一切正常测试失败。

我试图修改上面的代码以将Producer<String, KafkaEmailMessageWrapper> producer更改为private Producer<String, String> producer,然后在测试中发送JSON而不是KafkaEmailMessageWrapper实例,但随后出现异常:

java.lang.ClassCastException: java.lang.String cannot be cast to com.test.model.KafkaEmailMessageWrapper

因此,出于某种原因,从JSON字符串到模型的反序列化似乎在此测试场景中没有发生。我希望测试尽可能接近实际用例,因此它应该产生一个字符串消息,然后对我的模型类执行反序列化。不确定为什么不会发生这种情况,希望能帮助您理解为什么如此。

为了完成,这是消息侦听器的定义:

@KafkaListener(topics = "email-service")
public void receive(@Payload KafkaEmailMessageWrapper message)

编辑

让我重新描述一下问题,因为也许我有些困惑。 该应用的概述是:

  • 使用JSON消息
  • 将其序列化为KafkaEmailMessageWrapper
  • 发送电子邮件

这有效,我将JSON发送到Kafka,已收到(上面的侦听器定义)并正确进行。我唯一的问题是在测试时。

我想在测试中模仿相同的情况,但是当我这样做时:

kafkaTemplate.sendDefault("key", "<json message>");

我得到一个:

Cannot handle message; nested exception is  org.springframework.messaging.converter.MessageConversionException: Cannot convert from [java.lang.String] to [com.test.model.KafkaEmailMessageWrapper] for GenericMessage

但是,当我改为发送KafkaEmailMessageWrapper实例时:

kafkaTemplate.sendDefault("key", kafkaEmailMessageWrapper);

它可以工作,但是它不使用我在课程中指定的@JsonDeserialize批注:

@JsonDeserialize(using = SemicolonArrayDeserializer.class)
private String[] to;
@JsonDeserialize(using = SemicolonArrayDeserializer.class)
private String[] cc;

因此,在发送实例时,to和cc字段始终为空,因为在字段中已经是字符串数组,因此沿;拆分的反序列化逻辑不会做任何事情。这是反序列化方法:

public String[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
    ObjectMapper mapper = (ObjectMapper) p.getCodec();
    JsonNode node = mapper.readTree(p);
    return node.asText().split(";");
}

1 个答案:

答案 0 :(得分:0)

您需要显示堆栈跟踪,但是我猜想是因为您使用的是@SpringBootTest,所以真正的使用者(侦听器方法)也正在收到消息,这就是为什么您得到异常的原因。

不清楚您正在测试的对象(使用测试Consumer)。

更好的测试是将MailSender服务注入到侦听器Bean中,并在测试用例中注入模拟MailSender以验证其是否按预期工作。