Essential Microservice Testing

Unit and contract testing yield better microservices

TLDR

  • DO unit tests and contract tests to assess the functionality of your microservices.
  • DONT rely heavily on integrated testing to validate the quality of your software
  • If your exploratory or end to end testing discovers a bug that wasn’t found in your unit or contract testing, DONT write an integrated test to cover this possibility in the future if you can help it, instead move down the pyramid and write unit or contract tests that will cover this problem.
  • DO use integrated environments for exploratory testing and temporary integrated testing. Move those integrated tests down as soon as you can.
  • DO use integrated environments for more than your integration tests. You can run regression testing on the weekends or perhaps stress testing or security testing.
  • DO learn how to test in production safely as a better form of integration testing.

Types of Testing

There are many types of tests, enough to fill a book, to make it even more confusing, people often disagree on the definitions of those types of tests. For example integration tests are often defined as the testing of your application in an integrated environment against another live version of its dependencies to see how it behaves, but Martin Fowler prefers to refer to those “broad” integration tests as end to end tests, and “narrow” integration tests as something like contract testing. Sam Newman on the other hand seems to call these service testing and uses Martin Fowler definition of end to end tests to refer to broad integration testing.

So yes… it is confusing, and to avoid much of this confusion I am going to be using integration tests and end to end tests somewhat interchangeably, although end to end tests can include ui tests, smoke and exploratory testing as well, I like more the term integration tests to define those tests which are automated. I am not really going to be using the term service tests either because it sounds like unit testing with stubbing and mocking to me.

Unit Testing

This are the most important tests that you should run. They are easy to implement, fast and cheap to run and they should cover most of the issues related to the international functioning of your microservice. Unit tests are not just good to keep your code sane, they are also very good to make your code more testable. If your code is testable it is a lot more likely to be more clean and well designed, hence why test driven development is often recommended.

During unit testing you may use stubs to mimic responses.

Contract Testing (Consumer Driven Contracts)

Unit testing cannot cover all dependencies you have with other microservices. Also these microservices can and will change continuously so how do you ensure you can test against these changes in a fast, cheap and efficient way? The answer is contract testing. With contract testing you and other teams can create contracts that define what calls will be made and what responses are expected, a broker can be set up for this, or they can be stored as libraries and the test can fetch the latest version of these contracts and test the validity of the calls being made.

During contract testing you will use test doubles to mimic the replies that you get based on the contracts you have.

Common ways to do contract testing is something like pacts or Spring Cloud Contracts.

So why not do Integration Testing?

The most important thing to understand about integration testing (or end to end testing) is that it should not be relied on to test the functionality of your microservices. There are many drawbacks for this type of testing to be used this way:

  • Integrated environments have many dependencies that are outside of your control. A test may fail because of bad network, another service being down, someone breaking another service, etc. You want your tests to be deterministic, if they are not you will waste a lot of time and lose confidence in the validity of your testing.
  • Relying on integrated testing encourages the reliance on dependencies. If you as a developer aim to create independent microservices and with unit and service tests, then you will make sure they are as decoupled as possible and that your dependencies are clearly defined.
  • Responsibility for these tests is often shared, but not often clearly defined or well understood.
  • These tests are very slow compared to unit and contract testing.
  • Because it takes a lot longer, end to end testing can really pile up and become a bottleneck while you wait back from QA or other teams for approval that you can proceed, sometimes push freezes are involved if some issue is being fixed in the integrated environment.
  • You reduce your time to market and the agility of your organisation by relying on integrated tests.

There is an excellent talk by JB Rainsberger that I recommend on this topic.

Some valid uses of Integrated Environments

  • Run some integration tests when you are starting out, as long as you have the intention to do contract testing or unit testing later on.
  • They are also a good place for QA to do exploratory testing or end to end testing.
  • Cross functional testing such as security testing, stress, performance testing, etc.
  • Semantic monitoring
  • Okay for occasional test journeys

Additional Resources

https://www.utest.com/articles/bolton-and-bach-on-testing-vs-checking

https://adamcod.es/2014/05/15/test-doubles-mock-vs-stub.html

https://martinfowler.com/bliki/TestDouble.html

https://martinfowler.com/bliki/IntegrationTest.html

https://martinfowler.com/bliki/SyntheticMonitoring.html

https://www.softwaretestinghelp.com/types-of-software-testing/

https://www.oreilly.com/learning/building-microservices-testing

https://docs.pact.io/getting_started/sharing_pacts

https://codefresh.io/docker-tutorial/how-to-test-microservice-integration-with-pact/