Testing content-driven web app backends

Testing the backend of a web application is a critical part of the development process and any ongoing monitoring. Also, see testing for the frontend.

Test-driven development

In Test-Driven Development (TDD), application requirements are translated into test cases before an application is fully implemented. During development, these tests are written first and successively implemented as the application is built. Clear requirements (ie. test cases) ensure that the final code is well structured, meets all requirements, and is correct, which is especially important during the early stages of development.

Continuous Integration and Automated Testing

Continuous integration (CI) testing automatically runs tests against any code changes, such as during code reviews or when code has been merged into your code repository. Automated tests improve the overall quality and confidence in the submitted code by reducing the risks of breakages or regressions during development. It is recommended to set up an automated testing system for your environment to ensure the health of your application. Use a system supported by your architecture, platform, and language and seamlessly integrated into your development pipeline; for example, use GitHub Actions for a CI workflow or a custom CI pipeline built-in the cloud, customized for your configuration.

Learn more about the principles behind automated testing and how to improve your tests. Learn more about continuous integration testing and the best practices for implementing, setting up, and measuring success.

As the next step, consider an automated continuous delivery (CD) pipeline to automatically deploy changes and updates to your application.

Unit testing

Unit testing refers to testing small, self-contained parts of code in isolation. Use a unit testing framework that is recommended and popular for your backend language or framework. For example, for a monolithic Java-based application, use JUnit, or for a JavaScript-based serverless application (including Dart or TypeScript), use a framework built for Javascript testing, such as Jest.

Most modern backend frameworks have dedicated resources for testing. Consider integrating these features into your CI pipeline to automate testing. Ensure that your unit tests provide good code coverage of your application. Most testing frameworks provide features to analyze and report on your test coverage and allow for integration into your build pipeline.

Integration testing

Integration testing refers to testing larger modules or parts of your application together. Compared to unit testing (which focuses on individual parts of code), integration testing focuses on the integration of individual parts of your architecture. This may also include end-to-end flows covering multiple steps and modules across the application.

Integration tests may cover different modules of your application that may require interaction with external services, such as data storage, filesystems, or payments. Consider structuring your application to support abstractions for these services through dependency injection or similar features provided by your backend framework.

Behavior and functional testing

Approaching the backend (or individual modules or components) as an opaque box, functional testing focuses on the input and output of the system. While behavioral testing may be more common for the frontend, it also plays a vital role in confirming the end-to-end integrity of a backend system. These types of tests confirm that the system reacts and behaves as expected to different inputs.

Regression testing

Regression testing refers to tests that confirm that the application still behaves as expected. Tests successfully completed previously are re-run for any new changes to ensure that the changes have not reintroduced any previous issues. As bugs get fixed in an application, unit or integration tests should be added to ensure the bug doesn't recur. Regression tests should be integrated into the usual testing and build pipeline.

Smoke testing

Smoke testing, also called build verification testing, focuses on verifying the most critical functions of your backend application. Extending beyond integration testing, which abstracts some features and external dependencies, smoke testing covers the critical use cases for your application. Smoke testing can serve as an additional layer of verification before an application is promoted to the staging environment to ensure accurate behavior. Smoke tests may consist of automated unit tests or manual functional tests conducted by a QA team.

Production and staging environments

A staging environment is a copy of your production environment, sandboxed to facilitate testing. A dedicated deployment reduces the risk of issues with the production environment and makes it easier to conduct quality assurance. The staging environment lets you test a system close to the live production environment.

Having a 1-for-1 copy of the production environment may not be feasible due to factors such as cost or the complexity of the backend architecture. Consider what parts of the backend are the most critical and optimize those for a staging environment.

Testing against data used in production offers a great benefit to test how the application behaves with real-world data. Be sure to consider the privacy implications and data storage needs for such a staging environment, and carefully design the data used by your backend system. Access to such an environment should be tightly controlled, especially if production data is used.

Consider the scale of your system and whether a standing environment is appropriate for your application. Rolling staging environments that can be automatically deployed through a continuous delivery system also offer additional opportunities to test daily or weekly builds and give them additional scrutiny before release.

Another approach is to rely more heavily on continuous integration tests and automated systems rather than a fully deployed staging environment. Consider your team's workflow, system health, code coverage, and technical requirements.