Skip to content

Single shared KafkaEmbedded is all JUnit tests #666

@eugene-khyst

Description

@eugene-khyst

It seems like there is no convenient way to define single shared KafkaEmbedded that will be used in all JUnit tests.

There might be multiple tests that require KafkaEmbedded.
When @KafkaEmbedded is used with @ClassRule, Kafka server is started and stopped for every test class.
It may take a lot of time. Better to have embedded Kafka server started only once for all test classes.

Straightforward approach is to add to test sources configuration class defining a KafkaEmbedded bean:

@Configuration
public class KafkaEmbeddedConfig {

  @Bean
  public KafkaEmbedded kafkaEmbedded() {
    KafkaEmbedded kafkaEmbedded = new KafkaEmbedded(1, false, 1, "test_topic");
    kafkaEmbedded.setKafkaPorts(9092);
    return kafkaEmbedded;
  }
}

The problem is that there might tests creating multiple test contexts:

@TestPropertySource("classpath:test.properties")
@RunWith(SpringRunner.class)
@SpringBootTest
public class KafkaListenerTest {
...

and

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class RestControllerTest {
...

In this example Kafka server will be started twice. Second executed test class will fail with the exception

Caused by: kafka.common.KafkaException: Socket server failed to bind to localhost:9092: Address already in use.

if port is specified or new Kafka server will be started if random port is used.

Moreover, if @DirtiesContext is used, together with the refresh of the test context Kafka server will be restarted.

As a workaround KafkaEmbedded can be defined as a constant in some base test class that test classes will extend:

@ActiveProfiles("kafka")
public class KafkaTestBase {

  public static final KafkaEmbedded KAFKA_EMBEDDED = createKafkaEmbedded();

  private static KafkaEmbedded createKafkaEmbedded() {
    AnnotationConfigApplicationContext context =
        new AnnotationConfigApplicationContext(KafkaEmbeddedConfig.class);
    KafkaEmbedded kafkaEmbedded = context.getBean(KafkaEmbedded.class);
    Runtime.getRuntime().addShutdownHook(new Thread(context::close));
    return kafkaEmbedded;
  }
}
@TestPropertySource("classpath:test.properties")
@RunWith(SpringRunner.class)
@SpringBootTest
public class KafkaListenerTest extends KafkaTestBase {
...

This way Kafka server will be started in a separate Spring application context that will not be affected by @DirtiesContext or multiple test contexts (due to the usage of @AutoConfigureMockMvc etc.).

Can such approach be considered a normal solution or a workaround?
Are there any more efficient ways to have single shared KafkaEmbedded instance across all JUnit tests?

Sample:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions