Integration Testing Is The New Unit Testing: How To Ship Reliable Code In Less Time

Integration Testing Is The New Unit Testing: How To Ship Reliable Code In Less Time


4 min read

Unit test everything.

I hear this advice a lot.

Most people think you should focus most of your time on unit tests.

There's nothing wrong with unit testing, but it can slow you down in the long run. Unit testing is popular because it's fast and you can test every edge case. While this sounds good in theory, spending too much time on unit testing can lead to serious problems.

You end up with:

  • Brittle Tests
  • Too Many Tests
  • Unnecessary Tests

Focusing on unit testing leads to less valuable test suites

Unit tests are brittle.

I've written hundreds if not thousands of tests. Unit tests tend to break more often by design. Let's say you're testing a function. If the return type changes, any good test would have to break because the contract changed.

And if you're not careful you end up testing edge cases that aren't valid.

Imagine you have an add function that takes two numbers and returns the sum. Writing a test for this is easy. But if you don't check if the function arguments are numbers you can end up with tests that aren't valid.

it('should add two numbers', () => {
  expect(add(1, 2)).toEqual(3);

it('should concatenate two strings', () => {
  expect(add('Hello', ' World!')).toEqual('Hello World!');

The second test isn't wrong, but it's unlikely anyone will use the code that way.

This leads to test bloat.

Too many unit tests = slower CI / CD pipelines = slower development cycles

The tests are fast individually. But as you add more variations of a test, things start to get out of hand. If you're testing critical code that's used throughout your application then this tradeoff is worth it. But if you're testing non-critical code it starts to be less important.

Testing edge cases for code a user can't reach is a waste of time.

You end up with false tests. It's harder to refactor code because there are tests for irrelevant use cases. You're forced to stop and ask yourself if a test is valid.

The good news is that there's another way.

If you move away from unit testing, you can write fewer tests that provide more value. You can avoid testing implementation details. Your tests will be more stable and less likely to break as you change code.

Focus on integration tests instead of unit tests.

When you switch to integration tests, you need to shift your mental model around what you should test.

Here's how to get started.

Find a critical part of your code that you want to test and write down how a user would interact with it. For example, let's say you wanted to test a payment processing form.

This is how a typical user interaction flow would look:

  1. Enter a name
  2. Enter an address
  3. Enter credit card info
  4. Press the submit button
  5. Wait for a success message

All you need to do is to take those steps and convert them into a test.

Note: You might want to mock the request so you don't make an actual purchase in a test.

This one test provides more value than most unit tests. You're testing the entire payment flow. The test shouldn't break when you change the code unless you're changing that flow.

The integration test implicitly covers the unit tests.

You could write a unit test to make sure each field exists. You could also check if the input types are valid, but it's unnecessary. The integration test verifies those things without you having to write any assertions. There would be no way to go through the payment process if those unit tests failed.

Fewer tests. More coverage. Win win.

Integration testing leads to better test suites.

Unit tests still have their place but integration tests provide more value.

Focusing on unit tests leads to over-testing and slower feedback loops. You end up with tests for invalid use cases which makes it harder to refactor. The number of invalid tests can grow quickly and slow down your automated pipelines.

A slow pipeline means a slow development cycle.

Next time you write a test for a new feature, try writing an integration test so that your code is better tested.

I'm sure the result will speak for itself.