Testing Kafka consumer #KafkaListener with EmbeddedKafka in Spring Boot - spring-boot

I want to test my kafka consumer, but there is in issue with #EmbddedKafka.
#Slf4j
#Component
#RequiredArgsConstructor
public class KafkaEventConsumer {
private final CustomInterface customInterface;
#KafkaListener(topics = "test-topic")
public void consumeEvents(Event event) {
customInterface.apply(event);
}
}
}
My test class is as the following
#EmbeddedKafka
#ExtendWith(MockitoExtension.class)
#SpringBootTest(properties = "spring.kafka.bootstrap-servers=${spring.embedded.kafka.brokers}")
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class KafkaConsumerTest {
private Producer<String, String> producer;
private static File EVENT_JSON = Paths.get("src", "test", "resources", "files",
"event-file.json").toFile();
private KafkaEventConsumer kafkaEventConsumer;
#Mock
private CustomInterface CustomInterface;
#Autowired
private EmbeddedKafkaBroker embeddedKafkaBroker;
#Autowired
private ObjectMapper objectMapper;
#BeforeAll
void setUp() {
kafkaEventConsumer = new KafkaEventConsumer(customInterface);
Map<String, Object> configs = new HashMap<>(KafkaTestUtils.producerProps(embeddedKafkaBroker));
producer = new DefaultKafkaProducerFactory<>(configs, new StringSerializer(),
new StringSerializer()).createProducer();
}
#Test
void consumeEvents() throws IOException, BadCurrencyException {
var event = objectMapper.readValue(EVENT_JSON,Event.class);
String message = objectMapper.writeValueAsString(event);
producer.send(new ProducerRecord<>("test-topic", 0, "1", message));
producer.flush();
// Read the message and assert its properties
verify(customeInterface, timeout(10000).times(1)).apply(any());
}
#AfterAll
void shutdown() {
producer.close();
}
}
The test doesn't pass, the consumer didn't intercept the message
Wanted but not invoked:
customInterface.apply(
<any> );
Actually, there were zero interactions with this mock.
PS: I followed this interesting article

I used KafkaTemplate
#SpringBootTest
#EmbeddedKafka(brokerProperties = {"listeners=PLAINTEXT://localhost:9092"},
partitions = 1,
controlledShutdown = true)
class KafkaConsumerTest {
private static File EVENT_JSON = Paths.get("src", "test", "resources", "files",
"event-file.json").toFile();
#Autowired
KafkaTemplate<String, Event> kafkaTemplate;
#Autowired
private ObjectMapper objectMapper;
#SpyBean
private KafkaEvenConsumer kafkaEvenConsumer;
#SpyBean
private CustomInterface customInterface;
#Captor
ArgumentCaptor<Event> eventCaptor;
#Test
#SneakyThrows
#DirtiesContext
void consumeEvents() {
Event event = objectMapper.readValue(EVENT_JSON, Event.class);
kafkaTemplate.send("test-topic, "1", event);
verify(kafkaEventConsumer,timeout(10000).times(1)).consumeEvents(eventCaptor.capture());
Event argument = eventCaptor.getValue();
// .. assert the message properties
verify(customInterface, timeout(10000).times(1)).apply(any());
}
}

Related

Spring boot test KafkaTemplate

I have the service, that sending message
#Service
class ExportTaskService {
#Autowired
private KafkaTemplate<String, Object> template;
public void exportNewTask(ImportTaskRequest req) {
template.send('my-topic-name', req)
}
}
I configured beans: consumerFactory, producerFactory, kafkaTemplate (src/main/java)
If I run application, and execute metod -- all ok and message in real message broker.
Then I need spring test, that uses ExportTaskService.exportNewTask(request) and waiting message from same topic.
My code, but not working (i cant receive message):
#RunWith(SpringRunner.class)
#SpringBootTest
#DirtiesContext
#TestPropertySource(locations="classpath:test.properties")
#EnableKafka
#EmbeddedKafka(
topics = "new-bitrix-leads", ports = 9092
)
public class ExportingLeadTests {
#Autowired
private EmbeddedKafkaBroker embeddedKafkaBroker;
#Autowired
ExportTaskService exportTaskService;
#Autowired
ConsumerFactory<String, Object> consumerFactory;
#Test
public void test() throws InterruptedException {
assert(embeddedKafkaBroker != null);
assert(exportTaskService != null);
Consumer<String, Object> consumer = consumerFactory.createConsumer();
consumer.subscribe(Collections.singletonList("new-bitrix-leads"));
exportTaskService.exportNewTask(ImportTaskRequest.builder()
.description("descr")
.title("title")
.build());
ConsumerRecords<String, Object> records = consumer.poll(Duration.ofSeconds(3));
assert (records.count() == 1);
}
}
How I can read this message ? What I need to do ? I have no ideas...
BIG TNX :) !!
My simple solution is:
#SpringBootTest
#DirtiesContext
#TestPropertySource(locations="classpath:test.properties")
#EnableKafka
#EmbeddedKafka(
topics = "new-bitrix-leads", ports = 9092
)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class ExportingLeadTests {
private BlockingQueue<ConsumerRecord<String, Object>> records;
private KafkaMessageListenerContainer<String, String> container;
#Autowired
private EmbeddedKafkaBroker embeddedKafkaBroker;
#Autowired
ExportTaskService exportTaskService;
#BeforeAll
void setUp() {
DefaultKafkaConsumerFactory<String, Object> consumerFactory = new DefaultKafkaConsumerFactory<>(getConsumerProperties());
ContainerProperties containerProperties = new ContainerProperties("new-bitrix-leads");
container = new KafkaMessageListenerContainer<>(consumerFactory, containerProperties);
records = new LinkedBlockingQueue<>();
container.setupMessageListener((MessageListener<String, Object>) e -> records.add(e));
container.start();
ContainerTestUtils.waitForAssignment(container, embeddedKafkaBroker.getPartitionsPerTopic());
}
#AfterAll
void tearDown() {
container.stop();
}
private Map<String, Object> getConsumerProperties() {
Map<String, Object> map = new HashMap<>();
map.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, embeddedKafkaBroker.getBrokersAsString());
map.put(ConsumerConfig.GROUP_ID_CONFIG, "consumer");
map.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
map.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "10");
map.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "60000");
map.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
map.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
map.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
return map;
}
#Test
public void test() throws InterruptedException {
exportTaskService.exportNewTask(ImportTaskRequest.builder()
.description("descr")
.title("title")
.gclid("gclid")
.id("id")
.name("my name")
.website("http://website.com")
.yclid("yclid")
.source("Source")
.build());
ConsumerRecord<String, Object> record = records.poll(5, TimeUnit.SECONDS);
assert (record != null);
assertThat(record.value().toString(), containsString("gclid"));
}
}
really works. nice

Update Question: How to Mock MapToJson Class

this is an update to my previous question. I have a Utility class which I already manage to cover with my unit test except when the condition returns false.
Here is my class:
#Component
public class Utils {
#Autowired
private ObjectMapper mapper = new ObjectMapper();
#Autowired
private LoggingService loggingService;
public <E> String mapToJsonString(E object) {
try {
if (object == null) {
throw new IOException(ErrorMessage.ERROR_PROCESSING_JSON_NULL);
}else {
return mapper.enable(SerializationFeature.INDENT_OUTPUT).writeValueAsString(object); //NullPointer Here
}
} catch (IOException e) {
loggingService.logError(this.getClass().getName(), "1", ErrorMessage.ERROR_MAPPING_TO_JSONSTRING, e);
return "";
}
}
}
and here is my unit test
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = ATMMonitoringApplication.class, initializers = ConfigFileApplicationContextInitializer.class)
public class ObjectToJsonStringTest {
#SpyBean
private ATM atm;
#Autowired
private Utils utils;
#MockBean
private ObjectMapper mapper;
#MockBean
private LoggingService loggingService;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
myModelClass = new MyModelclass();
myModelClass.setStatus("U");
myModelClass.setTermCode("001");
myModelClass.setLocation("BGC");
}
#Test
public void testObjectToJson() throws JsonProcessingException {
String output = utils.mapToJsonStringmyModelClass
assertNotNull(output);
}
#Test
public void testObjectToJsonNull() throws JsonProcessingException {
String output = utils.mapToJsonString(null);
assertNull(output);
}
#Test
public void testJsonParsingException() {
myModelClass = new MyModelclass();
myModelClass = null;
String output = utils.mapToJsonString(myModelClass);
Mockito.when(loggingService.logError(this.getClass().getName(), "1", ErrorMessage.ERROR_MAPPING_TO_JSONSTRING, new Exception()))
.thenReturn("");
assertThat(output).isEmpty();
}
}
Stack trace says that i have a null pointer on this line of code:
return mapper.enable(SerializationFeature.INDENT_OUTPUT).writeValueAsString(object);
Please help me on this. Thanks
Solved it. I just change this
#Mock
private ObjectMapper mapper;
From #MockBean and it covered the whole class.

No bean found for definition [SpyDefinition... when using #SpyBean

I have an application that listens for Kafka messages using #KafkaListener inside of a #Component. Now I'd like to make an integration test with a Kafka test container (which spins up Kafka in the background). In my test I want to verify that the listener method was called and finished, however when I use #SpyBean in my test I get:
No bean found for definition [SpyDefinition#7a939c9e name = '', typeToSpy = com.demo.kafka.MessageListener, reset = AFTER]
I'm using Kotling, important classes:
Class to test
#Component
class MessageListener(private val someRepository: SomeRepository){
#KafkaListener
fun listen(records: List<ConsumerRecord<String, String>>) {
// do something with someRepository
}
}
Base test class
#ExtendWith(SpringExtension::class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class KafkaContainerTests {
// some functionality to spin up kafka testcontainer
}
Test class
class MessageListenerTest #Autowired constructor(
private val someRepository: SomeRepository
) : KafkaContainerTests() {
#SpyBean
private lateinit var messageListenerSpy: MessageListener
private var messageListenerLatch = CountDownLatch(1)
#BeforeAll
fun setupLatch() {
logger.debug("setting up latch")
doAnswer {
it.callRealMethod()
messageListenerLatch.count
}.whenever(messageListenerSpy).listen(any())
}
#Test
fun testListener(){
sendKafkaMessage(someValidKafkaMessage)
// assert that the listen method is being called & finished
assertTrue(messageListenerLatch.await(20, TimeUnit.SECONDS))
// and assert someRepository is called
}
}
The reason I am confused is that when I add the MessageListener to the #Autowired constructor of the MessageListenerTest it does get injected successfully.
Why is the test unable to find the bean when using #SpyBean?
It works fine for me with Java:
#SpringBootTest
class So58184716ApplicationTests {
#SpyBean
private Listener listener;
#Test
void test(#Autowired KafkaTemplate<String, String> template) throws InterruptedException {
template.send("so58184716", "foo");
CountDownLatch latch = new CountDownLatch(1);
willAnswer(inv -> {
inv.callRealMethod();
latch.countDown();
return null;
}).given(this.listener).listen("foo");
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
verify(this.listener).listen("foo");
}
}
#SpringBootApplication
public class So58184716Application {
public static void main(String[] args) {
SpringApplication.run(So58184716Application.class, args);
}
#Bean
public NewTopic topic() {
return TopicBuilder.name("so58184716").partitions(1).replicas(1).build();
}
}
#Component
class Listener {
#KafkaListener(id = "so58184716", topics = "so58184716")
public void listen(String in) {
System.out.println(in);
}
}

How to set up an automated integration test to check an aspect functionality with Spring-Boot

I've added an AOP (Aspect Oriented Programming) Aspect to my working project. It does work, but it won't be called when trying to Test it's functionality with an Integration Test.
The problem is, that the aspect is not called when the tests runs through. When using it normally it works fine.
I've tried to create a custom context which is supposed to be loaded for the integration tests, as I thought that the Aspect might not be loaded in the default context for these tests.
As this didn't work i also tried to manually proxy the bean of the aspect, but this didn't work neither.
Here's my integration test class:
#ComponentScan(basePackages = { "package.aspects" })
#RunWith(SpringRunner.class)
#SpringBootTest(classes = ZirafyApp.class)
#ContextConfiguration(classes ={ IntegrationTestAOPConfiguration.class })
public class CellResourceIntTest {
private static CellTestHelper helper = new CellTestHelper();
#Autowired
private PageableHandlerMethodArgumentResolver pageableHandlerMethodArgumentResolver;
#Autowired
private ExceptionTranslator exceptionTranslator;
#Autowired
private EntityManager em;
#Autowired
private BusinessFacade businessFacade;
#Autowired
private CellRepository cellRepository;
#Autowired
private AspectModule aspectModule;
private MockMvc restCellMockMvc;
private MappingJackson2HttpMessageConverter jacksonMessageConverter = new MappingJackson2HttpMessageConverter();
private Cell cell;
private Cell parentCell;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
final CellResource cellResource = new CellResource(cellRepository, businessFacade);
this.restCellMockMvc = MockMvcBuilders.standaloneSetup(cellResource)
.setCustomArgumentResolvers(pageableHandlerMethodArgumentResolver)
.setControllerAdvice(exceptionTranslator)
.setConversionService(createFormattingConversionService())
.setMessageConverters(jacksonMessageConverter).build();
}
#Test
#Transactional
public void update_cellDtoWithEmptyName_returnsHttpError422AndCellInDbIsNotUpdated() throws Exception {
AspectJProxyFactory factory = new AspectJProxyFactory(cellRepository);
factory.addAspect(aspectModule);
CellRepository cellRepository = factory.getProxy();
CellDto cellDtoToUpdate = new CellDto.Builder().id(2).name(null).x(-10).active(true).parent(1).build();
Cell parentCell = helper.createCell(1L);
Cell cellToUpdate = helper.createCell(2L);
cellRepository.saveAndFlush(parentCell);
cellRepository.saveAndFlush(cellToUpdate);
restCellMockMvc.perform(put("/api/cells/update")
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(cellDtoToUpdate)))
.andExpect(status().is(200));
Cell updatedCell = cellRepository.findOne(2L);
assertEquals(cellToUpdate.getX(), updatedCell.getX());
}
Here the configuration file for the integration test:
#Configuration
#EnableJpaRepositories(basePackages = {"package.repository"})
#ComponentScan("ch.post.pf.aspects")
#EnableAspectJAutoProxy(proxyTargetClass = true)
public class IntegrationTestAOPConfiguration {
#Autowired
private ExceptionTranslator exceptionTranslator;
#Autowired
private EntityManager em;
#Autowired
private CellConverter cellConverter;
#Autowired
private CellTreeService cellTreeService;
#Autowired
private CellService cellService;
#Autowired
private CellRepository cellRepository;
#Autowired
private BusinessFacade businessFacade;
#Autowired
private AspectModule aspectModule;
#Bean
public CellConverter returnCellConverter() {
return cellConverter;
}
#Bean
public AspectModule returnAspectModule() {
return null;//Aspects.aspectOf(AspectModule.class);
}
#Bean
public PageableHandlerMethodArgumentResolver returnPageableArgumentResolver() {
return new PageableHandlerMethodArgumentResolver();
}
#Bean
public ExceptionTranslator returnExceptionTranslator() {
return exceptionTranslator;
}
#Bean
#Primary
public EntityManager returnEntityManager() { return em; }
#Bean
public BusinessFacade returnBusinessFacade() {
return businessFacade;
}
#Bean
public CellTreeService returnCellTreeService() {
return cellTreeService;
}
#Bean
public CellService returnCellService() {
return cellService;
}
}
And here my aspect-file:
#Aspect
#Component
public class AspectModule {
private BusinessFacade businessFacade;
#Autowired
AspectModule(BusinessFacade businessFacade){
this.businessFacade = businessFacade;
}
#Pointcut("execution(* ch.post.pf.web.rest.CellResource.update(..))")
private void update() {}
#Around("update() && args(cell)")
public Object checkIsValidCell(ProceedingJoinPoint pjp, CellDto cell) {
System.out.println("Aspect was run");
final String message = canUpdate(cell);
if (message.equals("cell_valid")) {
try {
return pjp.proceed(); // Calls the usual update() function, if the cell is valid
} catch (Throwable e) {
System.out.println("Something went wrong with the aspects");
System.out.println(e.toString());
return null;
}
} else {
deleteIfCellWasEmpty(cell);
return ResponseUtil.unprocessableEntity(message);
}
}
}
The aspect should keep working as it does right now but it should also work in the integration tests, at the moment it isn't called at all inside those.

Simple embedded Kafka test example with spring boot

Edit FYI: working gitHub example
I was searching the internet and couldn't find a working and simple example of an embedded Kafka test.
My setup is:
Spring boot
Multiple #KafkaListener with different topics in one class
Embedded Kafka for test which is starting fine
Test with Kafkatemplate which is sending to topic but the
#KafkaListener methods are not receiving anything even after a huge sleep time
No warnings or errors are shown, only info spam from Kafka in logs
Please help me. There are mostly over configured or overengineered examples. I am sure it can be done simple.
Thanks, guys!
#Controller
public class KafkaController {
private static final Logger LOG = getLogger(KafkaController.class);
#KafkaListener(topics = "test.kafka.topic")
public void receiveDunningHead(final String payload) {
LOG.debug("Receiving event with payload [{}]", payload);
//I will do database stuff here which i could check in db for testing
}
}
private static String SENDER_TOPIC = "test.kafka.topic";
#ClassRule
public static KafkaEmbedded embeddedKafka = new KafkaEmbedded(1, true, SENDER_TOPIC);
#Test
public void testSend() throws InterruptedException, ExecutionException {
Map<String, Object> senderProps = KafkaTestUtils.producerProps(embeddedKafka);
KafkaProducer<Integer, String> producer = new KafkaProducer<>(senderProps);
producer.send(new ProducerRecord<>(SENDER_TOPIC, 0, 0, "message00")).get();
producer.send(new ProducerRecord<>(SENDER_TOPIC, 0, 1, "message01")).get();
producer.send(new ProducerRecord<>(SENDER_TOPIC, 1, 0, "message10")).get();
Thread.sleep(10000);
}
Embedded Kafka tests work for me with below configs,
Annotation on test class
#EnableKafka
#SpringBootTest(classes = {KafkaController.class}) // Specify #KafkaListener class if its not the same class, or not loaded with test config
#EmbeddedKafka(
partitions = 1,
controlledShutdown = false,
brokerProperties = {
"listeners=PLAINTEXT://localhost:3333",
"port=3333"
})
public class KafkaConsumerTest {
#Autowired
KafkaEmbedded kafkaEmbeded;
#Autowired
KafkaListenerEndpointRegistry kafkaListenerEndpointRegistry;
Before annotation for setup method
#Before
public void setUp() throws Exception {
for (MessageListenerContainer messageListenerContainer : kafkaListenerEndpointRegistry.getListenerContainers()) {
ContainerTestUtils.waitForAssignment(messageListenerContainer,
kafkaEmbeded.getPartitionsPerTopic());
}
}
Note: I am not using #ClassRule for creating embedded Kafka rather auto-wiring #Autowired embeddedKafka
#Test
public void testReceive() throws Exception {
kafkaTemplate.send(topic, data);
}
Hope this helps!
Edit: Test configuration class marked with #TestConfiguration
#TestConfiguration
public class TestConfig {
#Bean
public ProducerFactory<String, String> producerFactory() {
return new DefaultKafkaProducerFactory<>(KafkaTestUtils.producerProps(kafkaEmbedded));
}
#Bean
public KafkaTemplate<String, String> kafkaTemplate() {
KafkaTemplate<String, String> kafkaTemplate = new KafkaTemplate<>(producerFactory());
kafkaTemplate.setDefaultTopic(topic);
return kafkaTemplate;
}
Now #Test method will autowire KafkaTemplate and use is to send message
kafkaTemplate.send(topic, data);
Updated answer code block with above line
since the accepted answer doesn't compile or work for me. I find another solution based on https://blog.mimacom.com/testing-apache-kafka-with-spring-boot/ what I would like to share with you.
The dependency is 'spring-kafka-test' version: '2.2.7.RELEASE'
#RunWith(SpringRunner.class)
#EmbeddedKafka(partitions = 1, topics = { "testTopic" })
#SpringBootTest
public class SimpleKafkaTest {
private static final String TEST_TOPIC = "testTopic";
#Autowired
EmbeddedKafkaBroker embeddedKafkaBroker;
#Test
public void testReceivingKafkaEvents() {
Consumer<Integer, String> consumer = configureConsumer();
Producer<Integer, String> producer = configureProducer();
producer.send(new ProducerRecord<>(TEST_TOPIC, 123, "my-test-value"));
ConsumerRecord<Integer, String> singleRecord = KafkaTestUtils.getSingleRecord(consumer, TEST_TOPIC);
assertThat(singleRecord).isNotNull();
assertThat(singleRecord.key()).isEqualTo(123);
assertThat(singleRecord.value()).isEqualTo("my-test-value");
consumer.close();
producer.close();
}
private Consumer<Integer, String> configureConsumer() {
Map<String, Object> consumerProps = KafkaTestUtils.consumerProps("testGroup", "true", embeddedKafkaBroker);
consumerProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
Consumer<Integer, String> consumer = new DefaultKafkaConsumerFactory<Integer, String>(consumerProps)
.createConsumer();
consumer.subscribe(Collections.singleton(TEST_TOPIC));
return consumer;
}
private Producer<Integer, String> configureProducer() {
Map<String, Object> producerProps = new HashMap<>(KafkaTestUtils.producerProps(embeddedKafkaBroker));
return new DefaultKafkaProducerFactory<Integer, String>(producerProps).createProducer();
}
}
I solved the issue now
#BeforeClass
public static void setUpBeforeClass() {
System.setProperty("spring.kafka.bootstrap-servers", embeddedKafka.getBrokersAsString());
System.setProperty("spring.cloud.stream.kafka.binder.zkNodes", embeddedKafka.getZookeeperConnectionString());
}
while I was debugging, I saw that the embedded kaka server is taking a random port.
I couldn't find the configuration for it, so I am setting the kafka config same as the server. Looks still a bit ugly for me.
I would love to have just the #Mayur mentioned line
#EmbeddedKafka(partitions = 1, controlledShutdown = false, brokerProperties = {"listeners=PLAINTEXT://localhost:9092", "port=9092"})
but can't find the right dependency in the internet.
In integration testing, having fixed ports like 9092 is not recommended because multiple tests should have the flexibility to open their own ports from embedded instances. So, following implementation is something like that,
NB: this implementation is based on junit5(Jupiter:5.7.0) and spring-boot 2.3.4.RELEASE
TestClass:
#EnableKafka
#SpringBootTest(classes = {ConsumerTest.Config.class, Consumer.class})
#EmbeddedKafka(
partitions = 1,
controlledShutdown = false)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class ConsumerTest {
#Autowired
private EmbeddedKafkaBroker kafkaEmbedded;
#Autowired
private KafkaListenerEndpointRegistry kafkaListenerEndpointRegistry;
#BeforeAll
public void setUp() throws Exception {
for (final MessageListenerContainer messageListenerContainer : kafkaListenerEndpointRegistry.getListenerContainers()) {
ContainerTestUtils.waitForAssignment(messageListenerContainer,
kafkaEmbedded.getPartitionsPerTopic());
}
}
#Value("${topic.name}")
private String topicName;
#Autowired
private KafkaTemplate<String, Optional<Map<String, List<ImmutablePair<String, String>>>>> requestKafkaTemplate;
#Test
public void consume_success() {
requestKafkaTemplate.send(topicName, load);
}
#Configuration
#Import({
KafkaListenerConfig.class,
TopicConfig.class
})
public static class Config {
#Value(value = "${spring.kafka.bootstrap-servers}")
private String bootstrapAddress;
#Bean
public ProducerFactory<String, Optional<Map<String, List<ImmutablePair<String, String>>>>> requestProducerFactory() {
final Map<String, Object> configProps = new HashMap<>();
configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress);
configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
return new DefaultKafkaProducerFactory<>(configProps);
}
#Bean
public KafkaTemplate<String, Optional<Map<String, List<ImmutablePair<String, String>>>>> requestKafkaTemplate() {
return new KafkaTemplate<>(requestProducerFactory());
}
}
}
Listener Class:
#Component
public class Consumer {
#KafkaListener(
topics = "${topic.name}",
containerFactory = "listenerContainerFactory"
)
#Override
public void listener(
final ConsumerRecord<String, Optional<Map<String, List<ImmutablePair<String, String>>>>> consumerRecord,
final #Payload Optional<Map<String, List<ImmutablePair<String, String>>>> payload
) {
}
}
Listner Config:
#Configuration
public class KafkaListenerConfig {
#Value(value = "${spring.kafka.bootstrap-servers}")
private String bootstrapAddress;
#Value(value = "${topic.name}")
private String resolvedTreeQueueName;
#Bean
public ConsumerFactory<String, Optional<Map<String, List<ImmutablePair<String, String>>>>> resolvedTreeConsumerFactory() {
final Map<String, Object> props = new HashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress);
props.put(ConsumerConfig.GROUP_ID_CONFIG, resolvedTreeQueueName);
return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(), new CustomDeserializer());
}
#Bean
public ConcurrentKafkaListenerContainerFactory<String, Optional<Map<String, List<ImmutablePair<String, String>>>>> resolvedTreeListenerContainerFactory() {
final ConcurrentKafkaListenerContainerFactory<String, Optional<Map<String, List<ImmutablePair<String, String>>>>> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(resolvedTreeConsumerFactory());
return factory;
}
}
TopicConfig:
#Configuration
public class TopicConfig {
#Value(value = "${spring.kafka.bootstrap-servers}")
private String bootstrapAddress;
#Value(value = "${topic.name}")
private String requestQueue;
#Bean
public KafkaAdmin kafkaAdmin() {
Map<String, Object> configs = new HashMap<>();
configs.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress);
return new KafkaAdmin(configs);
}
#Bean
public NewTopic requestTopic() {
return new NewTopic(requestQueue, 1, (short) 1);
}
}
application.properties:
spring.kafka.bootstrap-servers=${spring.embedded.kafka.brokers}
This assignment is the most important assignment that would bind the embedded instance port to the KafkaTemplate and, KafkaListners.
Following the above implementation, you could open dynamic ports per test class and, it would be more convenient.

Resources