What are unit tests?
Unit tests are designed to test the smallest sensible ‘units’ of code in a software system in isolation. This isolation is achieved by using mock versions of any dependencies, which can be ‘spied’ on to ensure they are called correctly. This is one of the reasons why dependency injection is so good, as it allows you to pass in pretend versions of other code.
Essentially, unit tests allow a developer to ensure that their unit of code behaves in a predictable way, including any interactions with dependencies, given specific inputs.
They are most effective when they are fast to run, and can be run automatically during development or before a build.
They are not integration tests (https://stackoverflow.com/questions/5357601/whats-the-difference-between-unit-tests-and-integration-tests)
Well written unit tests also have the benefit of providing documentation for the interface the unit of code exposes to the rest of the system.
This can be very powerful, as any developer who has to work on something you have tested can refer to the tests an immediately get a clear definition of what the unit of code does, and how they should use it.
Why write unit tests?
Writing unit tests the right way will make your code better!
Writing unit tests for the sake of it, or to achieve coverage numbers may make your code better, but won’t necessarily. It is important to understand why unit testing is useful and what benefits it can offer before you get started.
Testing is a tool, just like anything else. It will not magically fix everything if it is not done properly.
Here are some of the potential benefits of maintaining a comprehensive set of unit tests:
- Helps to document your code for other developers and yourself.
- Flushes out bugs.
- Improves design decisions.
- Acts as buffer against low level regression bugs.
Read these for more information on the benefits/reasons for unit testing:
How to unit test:
The pattern for writing tests will basically be the same regardless of what the unit of code being tested is:
1) Create an instance of the unit of code that you wish to test. 2) Mock and spy on any dependencies that your unit of code interacts with. 3) Interact with the unit under test via its public interface. 4) Ensure that the unit under test behaves as expected after it is interacted with, including calling any mocked dependent code correctly.
If for any reason any of these steps is difficult to do, then you may have to rethink how you have written your code. This is why writing tests can help with design decisions. As a general rule if something is difficult to test, it may not be sufficiently modular and/or it may be too tightly coupled to other areas of the system.
For example, if you directly import a dependency inside a class, rather than passing it in via dependency injection, you will be forced to directly call the dependency. This may be what you want, but it may not be. Unit testing will force you to think about things like this.