Stop using the Agile Testing Pyramid: a case for writing fewer unit tests

I think it’s fair to say that writing tests is one of the more challenging aspects of developing software. I would even go so far as to say that writing tests is more difficult than writing production code. Should a specific piece of functionality be tested using unit tests, service tests or end-to-end tests? What should be mocked? How many tests do we need? What is a unit anyway? What is a component? Do I need to write my tests first?

In this post we’ll discuss the principles of testing and the problems in the Agile Testing Pyramid. In the follow-up post, we’ll discuss a better approach to testing.

Principles of Testing

The first thing you need to understand about testing is that it’s a necessary evil, a means to an end. As a developer, your goal is to write production code and deliver value to the end user. The less effort you can spend on testing, the better. It is extremely important to have enough quality tests so that you can make changes confidently. Just remember that writing tests, in itself, is not the goal.

The second thing about testing is that not all tests are created equal. You might have many tests and extremely high code coverage. But that doesn’t necessarily mean that your test suite is any good. Have you ever had a bug in production that your tests didn’t catch? I’ve personally seen tests that use so much mocking that they end up not testing anything. I’ve also seen tests without meaningful assertions. There is also the issue of whether the test is testing functionality or implementation details (more on this below).

The main problem I have with the Agile Testing Pyramid is that it encourages us to write too many unit tests. It has the assumption that every class and every method needs to be unit tested. Some experts say that 80% of all tests should be Unit tests. Others say there should be an order of magnitude more unit tests than Service tests. Canned advice like this is just nonsense.

We’ve reached a point in our industry where to do no testing is frowned upon and that’s a good thing. But what doesn’t seem to be clear to everyone is that over-testing should be frowned upon too. If you spend 50% of your time writing tests, and if your tests break every time you refactor your code, then you’re not going to move fast, are you? You need to find the sweet spot between having not enough and too many tests.

Here are some examples of what happens to our test suite when we write too many unit tests:

Redundant tests

Take a simple Restful Service that exposes CRUD APIs. If you are using Spring Boot or just following DDD practices, it’s very likely that you have the following class structure:

If you’re writing tests according to the Agile Testing Pyramid or even Spring Boot best practices, you’ll probably end up with the following tests:

  • Unit Test for Repository class
  • Unit Test for Service class
  • Unit Test for Controller class
  • Service Test at the API level

The thing about unit tests is that they don’t actually give you much confidence that the system is working as expected. The fact that individual units work properly doesn’t mean that the system as a whole works correctly. For this reason, the most important thing is to have Service Tests at the API level in order to prove that the service works as expected. If the logic in the controllers, services, and repositories is simple enough and it’s already covered by the Service Test, then they don’t need to be unit-tested independently.

Take for example the getById method in the following UserService class:

class UserService {	 	 
 private UserRepository userRepository;	 	 
 public User getById(int id) {	 	 
 return userRepository.getById(id);	 	 

I’ve seen many developers try to unit test a method like that. My recommendation for simple methods like this is not to bother with unit tests. As long as the method is covered by a proper Service test, having an additional unit test would be redundant in my opinion.

Low quality tests

Classes that have many dependencies can be pretty hard to unit test. You can tackle this in two ways. The first option is to mock all the dependencies of the class under test. If the number of dependencies to mock is high, you’ll end up with tests that:

  • are hard to setup and change: the behavior of each mock dependency has to be defined explicitly.
  • don’t give you much confidence: the more mocks a test uses, the less confidence it gives you (more on this in the next post).

So, what’s the other option then? Don’t test that class with a unit test – use the real dependencies. You don’t want to spend an hour writing a unit test that won’t give you much confidence and is hard to maintain when you can write a component test in five minutes that will give you very high confidence and it’s easy to maintain.

It’s true that using real dependencies will make the test run more slowly. In some cases this might be acceptable, in some cases, it won’t. The important thing is to make these sort of trade-offs consciously and to not just follow best practice blindly.

Testing implementation details

Of all testing anti-patterns, this is one of the worst in my opinions. Methods that don’t have explicit side effects or that call external resources like databases are very hard to unit test. Consider the following Repository class:

class UserRepository {
	private DBAdapter dbAdapter;
	public User getById(int id) {
		return dbAdapter.query("SELECT * FROM users WHERE id = " + id, User.class);


How can you unit test the getById method? I’ve seen tests like this:


public void test() {
	User user = userRepository.getById(1);
	verify(dbAdapter).query("SELECT * FROM users WHERE id = 1");


This test is not testing that the getById method does what it’s supposed to do – it’s only testing part of its implementation details.

The main problem with this sort of test is that whenever we make changes to how the UserRepository class works, we’ll need to change our tests too. For example, if we decide to use a different database adapter implementation or change what the query looks like, our test will break. This is not what you want from your tests. You want tests that act as safety nets so that you can refactor your code with confidence. For this to be true, your tests should test functionality and not implementation details. Basically, if a test is red it should be because existing functionality has been broken. If the functionality still works as expected, your tests should pass, even if internal implementation details have changed.

Also, tests like this don’t give us much confidence. What if the query is wrong?


While it’s true that unit tests run faster than service tests, there is more to testing than just running speed. We’ll discuss these aspects in the next post.

For now, understand that best practices, like the Agile Testing Pyramid, are not absolute truths and they don’t always apply. Not everything has to be covered by unit tests. There are many cases where a Service Test will be more than enough. There are also cases when using real dependencies instead of mocking will produce more valuable tests.

Testing principles and trade-offs