The Magic of Consumer Driven Contract Tests

James Kan
James Kan
Aug 27, 2020 · 10 min read

Preface

Why hello there! My goal in this article is to share what Consumer-Driven Contract Tests are, how to design them, how we use them at Hootsuite, and talk about some of the technical challenges that come with these tests. For simplicity, I’ll refer to Consumer-Driven Contract Tests as contract tests. So let’s get started!

What are Contract Tests?

Now before you think you are signing your life away, contract tests are quite an integral part of creating a reliable service-oriented architecture.

Contract Test Case Recipe

So, what makes up an individual contract test case? Well, let’s take a look at the following diagram…

  • Producers — Services Emitting events
  • Broker — Intermediate service that holds emitted events and provides them to consumers
  • Consumers — Services Consuming events
  • Create a contract testing service that executes them at scheduled intervals — proactive and can serve as health status for your services
  • Embed the tests inside a docker container, this way the time it takes to perform setup for contract test execution can be reduced.

Hootsuite Contract Tests with a Twist

At Hootsuite, Contract Tests are done with a little bit more complexity. To achieve more purposeful testing, phase one in the previous section has a bit of a twist. Instead of mocking the data, we can generate the necessary data for the producers by hitting the APIs of major social networks — Facebook, Twitter, Instagram, LinkedIn …etc. Once a Tweet or Post is created, if the social network accounts are linked to Hootsuite, our services will receive the creation event information and use that as the data for the contract tests! LIVE DATA!

Twitter Contract Tests

Recently, at Hootsuite, we did work to migrate our Twitter services from polling to Webhook, referred to as Twitter Migration. So why was this important for building out Contract Tests?

/*
Scenario: A registered user direct mentions a registered user
Expected Result: The receiving registered user should be assigned.
*/

"Creating a Twitter Mention" should "generate an assignment" in new TestCase {
// Preparing the request with the unique text we want to tweet
val request = generateCreatePostRequest(
messageBody = s"@$receiverName $randomDayQuote",
socialProfileId = senderSocialProfileId
)
// PHASE 1
// Create the tweet
whenReady(scumApi.createPosts(List(request))) {
// Wait for the resulting confirmation that a tweet has been made
// and save the response fields so we can use it to validate within
// our own system.
result: List[CreatePostResponse] =>
val response = result.headOption.get
response.postId shouldBe defined
// PHASE 2
// Checking broker
eventually {
// check if the upstream producer service, whether it successfully
// obtained a response and provided the correct event inside kafka
// by validating if the postId we got from tweet creation response
// matches the event's postId.
validateEventBus(response.postId.get)
}
// PHASE 3
// Checking if downstream services performed expected action and
// assert on them
eventually {
// check if message assigning service correctly assigned a
// message/post to a user
whenReady(isAssigned(response.postId.get, receiverOrgId)){
isAssigned =>
isAssigned shouldBe true
}
}
eventually {
// check if message has been tagged correctly and the correct number // of tags have been applied.
whenReady(getTags(response.postId.get)) { response =>
response.tags.length shouldBe 1
response.tags.head.id shouldBe tagId
}
}
}
}
  • Retrieving a message

Technical Challenges

Now, a couple of things I’ve encountered while creating these contract tests.

  • Accept the fact some tests may fail due to flakiness, so long as when evaluating the value of the test case, this test still highlights and covers an important part of your code, this may be an acceptable flakiness

Why is it Important?

Additional Functions of Contract Tests

  • Provide confidence in future changes (avoiding breaking changes) of these critical components
  • Help drive Test-Driven Development (TDD) — Once one test exists, it’ll open the door to more tests to write!

Concluding Remarks

Sure, contract tests may consume a good chunk of a developer’s time to implement and may be expensive to maintain. However, the value it can provide when applied to critical components of your application can be extremely high. Having effective and tests allow you to scale your product, teams, and company efficiently with reduced application outages! Your SLO’s will thank you later 😂

Hootsuite Engineering

Hootsuite's Engineering Blog