I think everyone has heard about the pyramid of tests. It's divided into three parts: Its bottom is built of unit tests. They're the base, and you're supposed to have more of them than any other type of tests. Integration tests are in the middle, and then you should have just a few end-to-end tests on the top.
Those're the classics as defined by Martin Fowler and recommended by, among others, Google. Or are they?
Another perspective on tests is to try to evaluate the added value of each test. Unit tests are cheap, fast, and easy to write — that's for sure. Generally, as you move up the pyramid, tests get harder to write, are slower to run and thus more expensive. That strongly suggests we should focus all our efforts on writing unit tests. But what about that added value?
As it turns out, when you move up the pyramid, the business value of tests significantly increases.
Well yes, but actually no. It's not inherently wrong. It's just that each level of tests plays a different role in the process of building software:
If you want to write clean, reusable code adhering to all the best practices, you should definitely use TDD. Divide your features into units of code. Write unit tests. Write the implementation. Refactor. Repeat. You'll end up with quite a lot of unit tests, sure, but they are cheap, fast, and isolated from the rest of the codebase, meaning the maintenance cost should also be low.
The added value here is that your codebase is cleaner.
But what if you already have an app, 0 tests, and you want to start writing tests to add some value to your product? Unit tests are not very useful for testing existing apps.
First of all, they only test units of work instead of actual features. Your users don't care about units of work, they care about your app working fine. Unit tests do not even attempt to guarantee that. That's not their point at all!
Moreover, unit testing of an existing codebase is challenging. You end up with an impenetrable network of stubs and mocks, complex hacks, and implementation tests just because your codebase was not structured for unit tests.
Oh, and just by the way, many of your unit tests can easily be replaced by a statically and strongly typed languages such as TypeScript or Reason.
You should use e2e tests when you want to make sure your app works. That's a bold statement, isn't it? Right. E2e tests are expensive to write, hard to maintain, take a long time to set up and run. Despite this, you probably want to have at least one of these e2e tests. One which follows the most "happy path" of your app, just like most of your users do. That way, if something crucial breaks, you'll notice!
The value added here is that you gain some level of certainty that your app works.
Write integration tests if you want to make sure your features work. Add them for existing features. Write new integration tests for new features. Integrations tests all the way. Why?
Well-written integration tests don't test units of code, they don't test implementation detail. They view your features the same way as your users do! At the same time, they still only aim to test certain features and not the whole app. That makes them cheaper and faster to run than e2e tests but more reliable and useful than unit tests.
Integration tests bring the greatest value to your app. If your business asks you "which tests are most beneficial from the $$$ point of view" — you know what to answer.
You got it.
tl;dr:
Followup: https://kentcdodds.com/blog/write-tests