Sprint Cloud - Kafka Streams Binder - Testing KafkaStreamsProcessor - spring-kafka

I've been trying to write unit tests for a KafkaStreamsProcessor. This is the code processor code
#EnableBinding(KafkaStreamsProcessor.class)
public class StockProcessor {
private static final Log LOG = LogFactory.getLog(StockProcessor.class);
#Autowired
private EddieClient client;
#Autowired
private InventoryRepository inventoryRepository;
#Autowired
private PermissionRepository permissionRepository;
/**
* Receive message from input queue
* Apply business logic
* Send to output queue
*
* #param inputMessage the message
* #return outputMessage
*/
#StreamListener(Processor.INPUT)
#SendTo(Processor.OUTPUT)
public KStream<?, OutputMessage> process(KStream<?, InputMessage> inputMessage){
return inputMessage
.map((key, value) -> {
LOG.info("::: processing message...");
// ... business logic
return new KeyValue<>(key, outputMessage);
});
}
}
aplication.yml
spring:
cloud:
stream:
kafka:
streams:
binder:
brokers:
- ${NX_KAFKA_SERVERS}
bindings:
input:
destination: ${NX_INPUT_TOPIC}
content-type: application/json
group: ${NX_PULL_GROUP_ID}
output:
destination: ${NX_OUTPUT_TOPIC}
content-type: application/json
group: ${NX_PUSH_GROUP_ID}
This is what I've read and tried to do in unit test
public class StockProcessorTest {
private static final String INPUT_TOPIC = "input-topic";
private static final String OUTPUT_TOPIC = "output-topic";
#SpyBean
private StockProcessor stockProcessor;
#MockBean
private EddieClient client;
#MockBean
private InventoryRepository inventoryRepository;
#MockBean
private PermissionRepository permissionRepository;
private TopologyTestDriver topologyTestDriver;
private TestInputTopic<String, InputMessage> inputTopic;
private TestOutputTopic<String, OutputMessage> outputTopic;
private Topology topology;
private Properties config;
KStream<String, InputMessage> inputMessageStream;
KStream<String, OutputMessage> outputMessageStream;
#Before
public void setup() {
config = new Properties();
config.setProperty(StreamsConfig.APPLICATION_ID_CONFIG, "app_id");
config.setProperty(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "foo:1234");
StreamsBuilder streamsBuilder = new StreamsBuilder();
inputMessageStream = streamsBuilder.stream(INPUT_TOPIC);
stockProcessor.process(inputMessageStream).to(OUTPUT_TOPIC);
topology = streamsBuilder.build();
topologyTestDriver = new TopologyTestDriver(topology, config);
//???
}
}
I don't really know if I'am going to right path here. I am using Jackson serializer. How can I create the inputTopic and outputTopic and test my business logic?
I can provide any further detail needed.
Thanks in advance

Here are some examples for how to unit test a Kafka Streams application based on Spring Cloud Stream - https://github.com/spring-cloud/spring-cloud-stream-samples/blob/master/kafka-streams-samples/kafka-streams-word-count/src/test/java/kafka/streams/word/count/WordCountProcessorApplicationTests.java
Also, here is a test suite that has some advanced examples: https://github.com/spring-cloud/spring-cloud-stream-samples/tree/master/kafka-streams-samples/kafka-streams-inventory-count/src/test/java/kafka/streams/inventory/count
These examples have details about how to use Serdes with the test driver.
Please check them out and see if they satisfy your testing requirements.

Related

How to test a kafka consumer against a real kafka broker running on a server?

I have difficulty understanding some Kafka concepts in Java Spring Boot. I’d like to test a consumer against a real Kafka broker running on a server, which has some producers that write / have already written data to various topics. I would like to establish a connection with the server, consume the data, and verify or process its content in a test.
An enormous majority of examples (actually all I have seen so far) in the internet refer to embedded kafka, EmbeddedKafkaBroker, and show both a producer and a consumer implemented on one machine, locally. I haven’t found any example that would explain how to make a connection with a remote kafka server and read data from a particular topic.
I've written some code and I've printed the broker address with:
System.out.println(embeddedKafkaBroker.getBrokerAddress(0));
What I got is 127.0.0.1:9092, which means that it is local, so the connection with the remote server has not been established.
On the other hand, when I run the SpringBootApplication I get the payload from the remote broker.
Receiver:
#Component
public class Receiver {
private static final String TOPIC_NAME = "X";
private static final Logger LOGGER = LoggerFactory.getLogger(Receiver.class);
private CountDownLatch latch = new CountDownLatch(1);
public CountDownLatch getLatch() {
return latch;
}
#KafkaListener(topics = TOPIC_NAME)
public void receive(final byte[] payload) {
LOGGER.info("received the following payload: '{}'", payload);
latch.countDown();
}
}
Config:
#EnableKafka
#Configuration
public class ByteReceiverConfig {
#Autowired
EmbeddedKafkaBroker kafkaEmbeded;
#Value("${spring.kafka.bootstrap-servers}")
private String bootstrapServers;
#Value("${spring.kafka.consumer.group-id}")
private String groupIdConfig;
#Bean
public KafkaListenerContainerFactory<?> kafkaListenerContainerFactory() {
final ConcurrentKafkaListenerContainerFactory<Object, Object> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
return factory;
}
#Bean
ConsumerFactory<Object, Object> consumerFactory() {
return new DefaultKafkaConsumerFactory<>(consumerProperties());
}
#Bean
Map<String, Object> consumerProperties() {
final Map<String, Object> properties =
KafkaTestUtils.consumerProps("junit-test", "true", this.kafkaEmbeded);
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, ByteArrayDeserializer.class);
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ByteArrayDeserializer.class);
properties.put(ConsumerConfig.GROUP_ID_CONFIG, groupIdConfig);
return properties;
}
Test:
#EnableAutoConfiguration
#EnableKafka
#SpringBootTest(classes = {ByteReceiverConfig.class, Receiver.class})
#EmbeddedKafka
#ContextConfiguration(classes = ByteReceiverConfig.class)
#TestPropertySource(properties = { "spring.kafka.bootstrap-servers=${spring.embedded.kafka.brokers}",
"spring.kafka.consumer.group-id=EmbeddedKafkaTest"})
public class KafkaTest {
#Autowired
private KafkaListenerEndpointRegistry kafkaListenerEndpointRegistry;
#Autowired
EmbeddedKafkaBroker embeddedKafkaBroker;
#Autowired
Receiver receiver;
#BeforeEach
void waitForAssignment() {
for (MessageListenerContainer messageListenerContainer : kafkaListenerEndpointRegistry.getListenerContainers()) {
System.out.println(messageListenerContainer.getAssignedPartitions().isEmpty());
System.out.println(messageListenerContainer.toString());
System.out.println(embeddedKafkaBroker.getTopics().size());
System.out.println(embeddedKafkaBroker.getPartitionsPerTopic());
System.out.println(embeddedKafkaBroker.getBrokerAddress(0));
System.out.println(embeddedKafkaBroker.getBrokersAsString());
ContainerTestUtils.waitForAssignment(messageListenerContainer,
embeddedKafkaBroker.getPartitionsPerTopic());
}
#Test
public void testReceive() {
}
}
I would like somebody to shed some light on the following issues:
1.Can an instance of the class EmbeddedKafkaBroker be used to test data that comes from a remote broker, or is it only used for local tests, in which I would procude i.e send data to a topic that I created and consume data myself?
2.Is it possible to write a test class for a real kafka server? For instance to verify if a connection has been establish, or if a data has been read from a specific topic. What annotations, configurations, and classes would be needed in such case?
3.If I only want to consume data, do I have to provide the producer configuration in a config file (it would be strange, but all examples I have encountered so far did it)?
4.Do you know any resources (books, websites etc.) that show real examples of using kafka i.e. with a remote kafka server, with a procuder or a consumer only?
You don't need an embedded broker at all if you want to talk to an external broker only.
Yes, just set the bootstrap servers property appropriately.
No, you don't need producer configuration.
EDIT
#SpringBootApplication
public class So56044105Application {
public static void main(String[] args) {
SpringApplication.run(So56044105Application.class, args);
}
#Bean
public NewTopic topic() {
return new NewTopic("so56044105", 1, (short) 1);
}
}
spring.kafka.bootstrap-servers=10.0.0.8:9092
spring.kafka.consumer.enable-auto-commit=false
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { So56044105Application.class, So56044105ApplicationTests.Config.class })
public class So56044105ApplicationTests {
#Autowired
public Config config;
#Test
public void test() throws InterruptedException {
assertThat(config.latch.await(10, TimeUnit.SECONDS)).isTrue();
assertThat(config.received.get(0)).isEqualTo("foo");
}
#Configuration
public static class Config implements ConsumerSeekAware {
List<String> received = new ArrayList<>();
CountDownLatch latch = new CountDownLatch(3);
#KafkaListener(id = "so56044105", topics = "so56044105")
public void listen(String in) {
System.out.println(in);
this.received.add(in);
this.latch.countDown();
}
#Override
public void registerSeekCallback(ConsumerSeekCallback callback) {
}
#Override
public void onPartitionsAssigned(Map<TopicPartition, Long> assignments, ConsumerSeekCallback callback) {
System.out.println("Seeking to beginning");
assignments.keySet().forEach(tp -> callback.seekToBeginning(tp.topic(), tp.partition()));
}
#Override
public void onIdleContainer(Map<TopicPartition, Long> assignments, ConsumerSeekCallback callback) {
}
}
}
There are some examples in this repository for bootstrapping real Kafka producers and consumers across a variety of configurations — plaintext, SSL, with and without authentication, etc.
Note: the repo above contains examples for the Effective Kafka book, which I am the author of. However, they can be used freely without the book and hopefully they make just as much sense on their own.
More to the point, here are a pair of examples for a basic producer and a consumer.
/** A sample Kafka producer. */
import static java.lang.System.*;
import java.util.*;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.*;
public final class BasicProducerSample {
public static void main(String[] args) throws InterruptedException {
final var topic = "getting-started";
final Map<String, Object> config =
Map.of(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092",
ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName(),
ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName(),
ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
try (var producer = new KafkaProducer<String, String>(config)) {
while (true) {
final var key = "myKey";
final var value = new Date().toString();
out.format("Publishing record with value %s%n",
value);
final Callback callback = (metadata, exception) -> {
out.format("Published with metadata: %s, error: %s%n",
metadata, exception);
};
// publish the record, handling the metadata in the callback
producer.send(new ProducerRecord<>(topic, key, value), callback);
// wait a second before publishing another
Thread.sleep(1000);
}
}
}
}
/** A sample Kafka consumer. */
import static java.lang.System.*;
import java.time.*;
import java.util.*;
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.serialization.*;
public final class BasicConsumerSample {
public static void main(String[] args) {
final var topic = "getting-started";
final Map<String, Object> config =
Map.of(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092",
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName(),
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName(),
ConsumerConfig.GROUP_ID_CONFIG, "basic-consumer-sample",
ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest",
ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
try (var consumer = new KafkaConsumer<String, String>(config)) {
consumer.subscribe(Set.of(topic));
while (true) {
final var records = consumer.poll(Duration.ofMillis(100));
for (var record : records) {
out.format("Got record with value %s%n", record.value());
}
consumer.commitAsync();
}
}
}
}
Now, these are obviously not unit tests. But with very little rework they could be turned into one. The next step would be to remove Thread.sleep() and add assertions. Note, since Kafka is inherently asynchronous, naively asserting a published message in a consumer immediately after publishing will fail. For a robust, repeatable test, you may want to use something like Timesert.

NodeRPCConnection.java instead NodeRPCConnection.kt

I want to connect RPC connection using java instead of kotlin. In corda 4.0 they have given using kotlin but I need it in java.
I have tried using java but its not working...
#RestController
#RequestMapping("/api/example/")
below code snippet from WebFlux module which connects to corda node using RPC:
#Component
public class NodeRPCConnection implements AutoCloseable {
// The host of the node we are connecting to.
#Value("${config.rpc.host}")
private String host;
// The RPC port of the node we are connecting to.
#Value("${config.rpc.username}")
private String username;
// The username for logging into the RPC client.
#Value("${config.rpc.password}")
private String password;
// The password for logging into the RPC client.
#Value("${config.rpc.port}")
private int rpcPort;
private CordaRPCConnection rpcConnection;
private CordaRPCOps proxy;
#PostConstruct
public void initialiseNodeRPCConnection() {
NetworkHostAndPort rpcAddress = new NetworkHostAndPort(host, rpcPort);
CordaRPCClient rpcClient = new CordaRPCClient(rpcAddress);
rpcConnection = rpcClient.start(username, password);
proxy = rpcConnection.getProxy();
}
#PreDestroy
public void close() {
rpcConnection.notifyServerAndClose();
}
public CordaRPCOps getProxy() {
return proxy;
}
}

Spring Security - Integration Tests with user

I have an integration test for Spring (with Spring Security) that looks like this:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RootControllerIT {
#LocalServerPort
private int port;
private URL base;
#Autowired
private TestRestTemplate template;
#Before
public void setUp() throws Exception {
this.base = new URL("http://localhost:" + port + "/");
}
#Test
#WithMockUser
public void getHello() throws Exception {
ResponseEntity<String> response = template.getForEntity(base.toString(),
String.class);
assertThat(response.getBody(), equalTo("OK"));
}
}
My issue is that the #WithMockUser does not work here, is there a way to add a mock user to the TestRestTemplte or otherwise send authenticated user information to the Integration test?
The mock user works fine when using MockMvc to test, but it's obviously not appropriate here.

Cometd with Spring-MVC for personalized chatting

I am working in a Spring-MVC application and I would like to include personalized chat as a feature in it. After some research I found out Cometd to be a suitable option. After going through the documentation and forever repeating samples, I have a little bit of setup which I have done. I need some help to integrate a personalized chat service in the spring-mvc app, and enabling private chat when user pushes chat button.
So basically, I found out, "/service/chat" can be used for private chat, so I have a class for that, and to use private chat, I must have a mapping of userid<-->sessionId, but I cannot find examples anywhere how to do it. I am posting some of the code I have, kindly let me know what is remaining to do, and if possible, some resources, samples for that.
Controller code:
#Controller
#Singleton
public class MessageController {
private MessageService messageService;
#Autowired(required = true)
#Qualifier(value ="messageService")
public void setMessageService(MessageService messageService){this.messageService=messageService;}
#RequestMapping(value = "/startchatting", produces = "application/text")
#ResponseBody
public String startChattingService(){
return "OK";
}
#RequestMapping(value = "/stopchatting",produces = "application/text")
#ResponseBody
public String stopChatting(){
return "OK";
}
}
Private Message Service :
#Service
public class PrivateMessageService {
#Session
private ServerSession session;
#Listener("/service/private")
public void handlePrivateMessage(ServerSession sender, ServerMessage message){
String userId = (String) message.get("targetUserId");
//Mapping code necessary to map userids to session-id's.
//ServerSession recipient = findServerSessionFromUserId(userId);
//recipient.deliver(session,message.getChannel(),message.getData(),null);
}
}
CometConfigurer :
#Component
#Singleton
public class CometConfigurer {
private BayeuxServer bayeuxServer;
private ServerAnnotationProcessor processor;
#Inject
public void setBayeuxServer(BayeuxServer bayeuxServer){this.bayeuxServer = bayeuxServer;}
#PostConstruct
public void init() {this.processor= new ServerAnnotationProcessor(bayeuxServer);}
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
System.out.println("Configuring service " + name);
processor.processDependencies(bean);
processor.processConfigurations(bean);
processor.processCallbacks(bean);
return bean;
}
public Object postProcessAfterInitialization(Object bean, String name) throws BeansException {
return bean;
}
public void postProcessBeforeDestruction(Object bean, String name) throws BeansException {
processor.deprocessCallbacks(bean);
}
#Bean(initMethod = "start", destroyMethod = "stop")
public BayeuxServer bayeuxServer() {
BayeuxServerImpl bean = new BayeuxServerImpl();
// bean.setOption(BayeuxServerImpl.LOG_LEVEL, "3");
return bean;
}
public void setServletContext(ServletContext servletContext) {
servletContext.setAttribute(BayeuxServer.ATTRIBUTE, bayeuxServer);
}
}
Cometd beans :
<beans:bean id="bayeuxServer" class="org.cometd.server.BayeuxServerImpl" init-method="start" destroy-method="stop"/>
I have directly included the JSP files which have cometd configuration and setup from https://github.com/fredang/cometd-spring-example, and modified them to serve my needs. Kindly let me know what else is remaining, all suggestions are welcome, I am unable to find any examples for same task on net, which are detailed, and have more code then explanation. Thank you.
Using Spring 4.x's new WebSocket feature would definitely work; moreover, this new module ships with lots of very interesting features for your use case:
STOMP protocol support
messaging abstractions
session management
pub/sub mechanisms
etc
You can check this nice chat application that demonstrates all those features.

How to deploy EJB 3.1 session bean in a war on Websphere 8.5

Now I use Jersey to create restful web service on Websphere 8.5. I also want the restful web service has the EJB 3.1's capacity.
My restful web service code as follow:
#Stateless
#Path("/tagServiceRS/{tagid}")
#Interceptors(TestTagServiceInterceptor.class)
public class TagServiceRS implements Serializable{
private static final long serialVersionUID = 5L;
private static final Logger log = LoggerFactory.getLogger(TagServiceRS.class);
#EJB
private TagTestService tagTestService;
#PersistenceContext(unitName = "tag-ejb")
private EntityManager entityManager;
#GET
#Produces("text/plain")
public String findTagById(#PathParam("tagid") String tagid) {
return "TAG";
}
/**
* #return the tagTestService
*/
public TagTestService getTagTestService() {
return tagTestService;
}
/**
* #param tagTestService the tagTestService to set
*/
public void setTagTestService(TagTestService tagTestService) {
this.tagTestService = tagTestService;
}
When I deploy the war on the Websphere 8.5. The TagServiceRS was successful deployed as a restful web service.And I test it. It's OK.But the TagServiceRS was failed deploy as a EJB session bean. The TagServiceRS's entityManager and tagTestService fields are all null.
I see the log, there is no error or warning log.
Below is my TagTestServiceBean code.
#Stateless
public class TagTestServiceBean implements TagTestService, Serializable {
private static final long serialVersionUID = 5L;
private static final Logger log = LoggerFactory.getLogger(TagTestServiceBean.class);
#Override
public Tag testFindTagById(Long id) {
log.info("testFindTagById ++++++++++++++++++++++++++++++++++++++++ invoked for id: {}", id);
return new Tag();
}
}
#Remote
public interface TagTestService extends Serializable {
/**
* #param id
* the ID from database
* #return a tag, may null
*/
Tag testFindTagById(Long id);
}
If Any Answers.Thanks a lot.
Change the annotation from #EJB to #Resource
#Resource
private TagTestService tagTestService;
The class itself need not to be annotated with #Stateless annotation.
Additionally, JAX-RS root resource and provider classes must have a JCDI specified scope. Scopes control the lifecycle of a JCDI managed bean. Root resource classes can have any valid scope such as #javax.enterprise.context.RequestScoped, which makes the JAX-RS root resource class behave the same as in a non-JCDI enabled application. The javax.ws.rs.core.Application subclasses and JAX-RS providers must have the #javax.enterprise.context.ApplicationScoped annotation.
More info here.

Resources