web-dev-qa-db-fra.com

Test d'un @KafkaListener à l'aide de Spring Embedded Kafka

J'essaie d'écrire un test unitaire pour un écouteur Kafka que je développe à l'aide de Spring Boot 2.x. Étant un test unitaire, je ne veux pas démarrer un serveur complet Kafka une instance de Zookeeper. J'ai donc décidé d'utiliser Spring Embedded Kafka.

La définition de mon auditeur est très basique.

@Component
public class Listener {
    private final CountDownLatch latch;

    @Autowired
    public Listener(CountDownLatch latch) {
        this.latch = latch;
    }

    @KafkaListener(topics = "sample-topic")
    public void listen(String message) {
        latch.countDown();
    }
}

Le test, qui vérifie que le compteur latch est égal à zéro après avoir reçu un message, est également très facile.

@RunWith(SpringRunner.class)
@SpringBootTest
@DirtiesContext
@EmbeddedKafka(topics = { "sample-topic" })
@TestPropertySource(properties = { "spring.kafka.bootstrap-servers=${spring.embedded.kafka.brokers}" })
public class ListenerTest {

    @Autowired
    private KafkaEmbedded embeddedKafka;

    @Autowired
    private CountDownLatch latch;

    private KafkaTemplate<Integer, String> producer;

    @Before
    public void setUp() {
        this.producer = buildKafkaTemplate();
        this.producer.setDefaultTopic("sample-topic");
    }

    private KafkaTemplate<Integer, String> buildKafkaTemplate() {
        Map<String, Object> senderProps = KafkaTestUtils.producerProps(embeddedKafka);
        ProducerFactory<Integer, String> pf = new DefaultKafkaProducerFactory<>(senderProps);
        return new KafkaTemplate<>(pf);
    }

    @Test
    public void listenerShouldConsumeMessages() throws InterruptedException {
        // Given
        producer.sendDefault(1, "Hello world");
        // Then
        assertThat(latch.await(10L, TimeUnit.SECONDS)).isTrue();
    }
}

Malheureusement, le test échoue et je ne comprends pas pourquoi. Est-il possible d'utiliser une instance de KafkaEmbedded pour tester une méthode marquée avec l'annotation @KafkaListener?

Tout le code est partagé dans mon référentiel GitHub kafka-listener .

Merci à tous.

12
riccardo.cardin

Vous envoyez probablement le message avant que le consommateur se soit vu attribuer le sujet/la partition. Définir la propriété ...

spring:
  kafka:
    consumer:
      auto-offset-reset: earliest

... il est par défaut latest.

C'est comme utiliser --from-beginning Avec le consommateur de console.

MODIFIER

Oh; vous n'utilisez pas les propriétés de démarrage.

Ajouter

props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");

EDIT2

BTW, vous devriez probablement aussi faire une get(10L, TimeUnit.SECONDS) sur le résultat de la template.send() (a Future<>) Pour affirmer que l'envoi a réussi.

EDIT

Pour remplacer la réinitialisation du décalage uniquement pour le test, vous pouvez faire la même chose que pour les adresses de courtier:

@Value("${spring.kafka.consumer.auto-offset-reset:latest}")
private String reset;

...

    props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, this.reset);

et

@TestPropertySource(properties = { "spring.kafka.bootstrap-servers=${spring.embedded.kafka.brokers}",
        "spring.kafka.consumer.auto-offset-reset=earliest"})

Cependant, gardez à l'esprit que cette propriété ne s'applique que la première fois qu'un groupe consomme. Pour toujours commencer à la fin à chaque démarrage de l'application, vous devez rechercher la fin lors du démarrage.

En outre, je recommanderais de définir enable.auto.commit Sur false pour que le conteneur se charge de valider les décalages plutôt que de simplement compter sur le client consommateur qui le fait selon un calendrier.

8
Gary Russell