docs/site/Defining-your-testing-strategy.md
{% include previous.html content=" This article continues from Defining the API using code-first approach. " %}
It may be tempting to overlook the importance of a good testing strategy when starting a new project. Initially, as the project is small and you mostly keep adding new code, even a badly-written test suite seems to work well. However, as the project grows and matures, inefficiencies in the test suite can severely slow down progress.
A good test suite has the following properties:
References:
To create a great test suite, think smaller and favor fast, focused unit-tests over slow application-wide end-to-end tests.
Say you are implementing the "search" endpoint of the Product resource described earlier. You might write the following tests:
One "acceptance test", where you start the application, make an HTTP request to search for a given product name, and verify that expected products were returned. This verifies that all parts of the application are correctly wired together.
Few "integration tests" where you invoke ProductController API from
JavaScript/TypeScript, talk to a real database, and verify that the queries
built by the controller work as expected when executed by the database
server.
Many "unit tests" where you test ProductController in isolation and verify
that the controller handles all different situations, including error paths
and edge cases.
Here is what your testing workflow might look like:
Write an acceptance test demonstrating the new feature you are going to build. Watch the test fail with a helpful error message. Use this new test as a reminder of what is the scope of your current work. When the new tests passes then you are done.
Think about the different ways how the new feature can be used and pick one that's most easy to implement. Consider error scenarios and edge cases that you need to handle too. In the example above, where you want to search for products by name, you may start with the case when no product is found.
Write a unit-test for this case and watch it fail with an expected (and helpful) error message. This is the "red" step in Test Driven Development (TDD).
Write a minimal implementation need to make your tests pass. Building up on the example above, let your search method return an empty array. This is the "green" step in TDD.
Review the code you have written so far, and refactor as needed to clean up the design. Don't forget to keep your test code clean too! This is the "refactor" step in TDD.
Repeat the steps 2-5 until your acceptance test starts passing.
When writing new unit tests, watch out for situations where your tests are asserting on how the tested objects interacted with the mocked dependencies, while making implicit assumptions about what is the correct usage of the dependencies. This may indicate that you should add an integration test in addition to a unit test.
For example, when writing a unit test to verify that the search endpoint is building a correct database query, you would usually assert that the controller invoked the model repository method with an expected query. While this gives us confidence about the way the controller is building queries, it does not tell us whether such queries will actually work when they are executed by the database server. An integration test is needed here.
To summarize:
See Testing Your Application for a reference manual on automated tests.