On this post I'll try to answer some of the questions that I asked myself when writing tests. There is plenty of information on the internet about what an unit test is, some katas to start with it, etc but there is a big difference between the typical example and production code. You'll also find what an integration test is, or a mock, or what TDD is as there are enough information out there
To mock or not to mock
So once you're into testing and discover mocks you use them for every class you use as a dependency. As we have been told, unit tests can't fail because some external dependency, that this is an integration. In my humble opinion, this is an error. Mocking should be only used when it's really necessary, when the effort for using the dependency is huge or will make your test suite slow. I'd include in there external libraries or their wrappers, database/repositories, sending emails, writing to a file, etc.
What are the disadvantages of mocks?
The main problem is that your test is coupled to the implementation. When you have that feeling that you're writing the same lines of codes than the real implementation, that's coupling. You'll have some calls to your mock with what it's exactly receiving, with how many arguments, what the return value is. It can generate a monster of mocking objects
What's the solution then?
Apply TDD and avoid creating mocks for you own classes unless than necessary. I have found myself doing this: creating some class, then writing the tests for it, and finally refactoring it to extract the logic in some other class. My tests are already covering both classes, but I'm mocking now the extracted class, so I need to create again some other test for the extracted class as otherwise no test will execute it.
Of course, the disadvantage of not mocking is the setup of the objects can be really complex. You have two dependencies which at the same time have more and then it grows exponentially. This is for me a code smell and remember that you're already mocking the database, your redis queue, etc.
Unit vs Integration tests
Imagine the case of the image, some isolated unit tests but the application breaking. This is were integration tests are useful. Some cases are, an external library like a queue system where you can test if a job is inserted on redis. Generation of payments getting/inserting data into the database, using some fixtures in the setUp() and deleting the rows inserted in the tearDown(). External API calls using a fake server is another example. But don't use them for increasing the coverage of your code.
The image from the article No more end to end tests describes how I think they should be distributed. It's mentioned a 70/20/10 separation: 70% unit tests, 20% integration tests, and 10% end-to-end tests. Functional tests or whatever they are called nowadays, are an important part for testing the UI of the application but the cost of maintaining and writing them is too high. As a developer I think you shouldn't write them. If you're lucky enough having a QA member in your team helping, it should be part of his job. Functional tests should cover the main functionality and some bugs caused in the past, that's all.
How much coverage?
For me, the aim is 100% and this is where you should point. If you set a minimum limit, let say, 80%, does this mean that you should only cover 4 of your 5 methods on your class? Specially if you're doing TDD you should have a highest number.
If you are testing thoughtfully and well, I would expect a coverage percentage in the upper 80s or 90s. I would be suspicious of anything like 100% - it would smell of someone writing tests to make the coverage numbers happy, but not thinking about what they are doing.-- Martin Fowler
I usually exclude from the coverage:
Entities, DTOs, Queries and Repositories, specific classes from the framework and the glue between the client and the backend side like Controllers.
Other important points
Law of Demeter
This is how the Law of Demeter is summarized:
- You can play with yourself. - You can play with your own toys (but you can't take them apart), - You can play with toys that were given to you. - And you can play with toys you've made yourself.
Basically it says you need to avoid method chains, like using a getter from an injected object which returns another object with another getter. Let say User->getCountry()->getCity()->getName(), this is usually solved having a city property on the User object.
How to test APIs?
As you want to know if the interaction with the API is working properly, but you can't use a production environment for that, you can build your own API mock server. You can simulate the API behaviour writing some blueprints using for example Apiary. With that, you'll have a way to test your API integrations or prototype a new one.
One important thing, if you're using internals APIs and not propietary APIs like Twitter, is that the integration tests you're writing can be executed by the other teams of your company building these APis on their testsuites. This is a faster way to know about breaking changes or possible bugs.
What time is it?
When dealing with tests using some time related functions, you can be on trouble if you can't freeze the time. For that, exists multiple libraries or built-in classes that can help you depending of the language/framework you use. For example, on Symfony, I'm using ClockMock.
This help you avoiding a test failing on a process that can be only executed the first day of the month. As a curious example, we were having some tests failing in our test suite depending of the timezone they were executed. Try to change your timezone to something like UTC+5 and execute it to be sure.