Mocked Streams 1.6.0 (git) is a library for Scala 2.11 and 2.12 which allows you to unit-test processing topologies of Kafka Streams applications (since Apache Kafka >=0.10.1) without Zookeeper and Kafka Brokers. Further, you can use your favourite Scala testing framework e.g. ScalaTest and Specs2. Mocked Streams is located at the Maven Central Repository, therefore you just have to add the following to your SBT dependencies:
libraryDependencies += "com.madewithtea" %% "mockedstreams" % "1.6.0" % "test"
Mocked Streams Version | Apache Kafka Version |
---|---|
1.6.0 | 1.0.1.0 |
1.5.0 | 1.0.0.0 |
1.4.0 | 0.11.0.1 |
1.3.0 | 0.11.0.0 |
1.2.1 | 0.10.2.1 |
1.2.0 | 0.10.2.0 |
1.1.0 | 0.10.1.1 |
1.0.0 | 0.10.1.0 |
It wraps the org.apache.kafka.test.ProcessorTopologyTestDriver class, but adds more syntactic sugar to keep your test code simple:
import com.madewithtea.mockedstreams.MockedStreams
val input = Seq(("x", "v1"), ("y", "v2"))
val exp = Seq(("x", "V1"), ("y", "V2"))
val strings = Serdes.String()
MockedStreams()
.topology { builder => builder.stream(...) [...] }
.input("topic-in", strings, strings, input)
.output("topic-out", strings, strings, exp.size) shouldEqual exp
It also allows you to have multiple input and output streams. If your topology uses state stores you need to define them using .stores(stores: Seq[String]):
import com.madewithtea.mockedstreams.MockedStreams
val mstreams = MockedStreams()
.topology { builder => builder.stream(...) [...] }
.input("in-a", strings, ints, inputA)
.input("in-b", strings, ints, inputB)
.stores(Seq("store-name"))
mstreams.output("out-a", strings, ints, expA.size) shouldEqual(expectedA)
mstreams.output("out-b", strings, ints, expB.size) shouldEqual(expectedB)
The records provided to the mocked stream will be submitted to the topology during the test in the order in which they appear in the fixture. You can also submit records multiple times to the same topics, at various moments in your scenario.
This can be handy to validate that your topology behaviour is or is not dependent on the order in which the records are received and processed.
In the example below, 2 records are first submitted to topic A, then 3 to topic B, then 1 more to topic A again.
val firstInputForTopicA = Seq(("x", int(1)), ("y", int(2)))
val firstInputForTopicB = Seq(("x", int(4)), ("y", int(3)), ("y", int(5)))
val secondInputForTopicA = Seq(("y", int(4)))
val expectedOutput = Seq(("x", 5), ("y", 5), ("y", 7), ("y", 9))
val builder = MockedStreams()
.topology(topologyTables)
.input(InputATopic, strings, ints, firstInputForTopicA)
.input(InputBTopic, strings, ints, firstInputForTopicB)
.input(InputATopic, strings, ints, secondInputForTopicA)
When you define your state stores via .stores(stores: Seq[String]) since 1.2, you are able to verify the state store content via the .stateTable(name: String) method:
import com.madewithtea.mockedstreams.MockedStreams
val mstreams = MockedStreams()
.topology { builder => builder.stream(...) [...] }
.input("in-a", strings, ints, inputA)
.input("in-b", strings, ints, inputB)
.stores(Seq("store-name"))
mstreams.stateTable("store-name") shouldEqual Map('a' -> 1)
When you define your state stores via .stores(stores: Seq[String]) since 1.2 and added the timestamp extractor to the config, you are able to verify the window state store content via the .windowStateTable(name: String, key: K) method:
import com.madewithtea.mockedstreams.MockedStreams
val props = new Properties
props.put(StreamsConfig.TIMESTAMP_EXTRACTOR_CLASS_CONFIG,
classOf[TimestampExtractors.CustomTimestampExtractor].getName)
val mstreams = MockedStreams()
.topology { builder => builder.stream(...) [...] }
.input("in-a", strings, ints, inputA)
.stores(Seq("store-name"))
.config(props)
mstreams.windowStateTable("store-name", "x") shouldEqual someMapX
mstreams.windowStateTable("store-name", "y") shouldEqual someMapY
Sometimes you need to pass a custom configuration to Kafka Streams:
import com.madewithtea.mockedstreams.MockedStreams
val props = new Properties
props.put(StreamsConfig.TIMESTAMP_EXTRACTOR_CLASS_CONFIG, classOf[CustomExtractor].getName)
val mstreams = MockedStreams()
.topology { builder => builder.stream(...) [...] }
.config(props)
.input("in-a", strings, ints, inputA)
.input("in-b", strings, ints, inputB)
.stores(Seq("store-name"))
mstreams.output("out-a", strings, ints, expA.size) shouldEqual(expectedA)
mstreams.output("out-b", strings, ints, expB.size) shouldEqual(expectedB)