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.
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.