The Testing Pyramid

The testing pyramid is a framework for balancing test types. At the base: many fast unit tests. In the middle: moderate integration tests. At the top: few slow end-to-end (E2E) tests. This structure maximizes confidence while minimizing test execution time. At Nexis Limited, we follow this pyramid across all products, with adaptations based on each product's architecture.

Unit Tests

Unit tests verify individual functions, classes, or modules in isolation. They are fast (milliseconds per test), easy to debug (a failure points directly to the problem), and provide the most granular feedback. Best practices:

  • Test one thing per test — each test should have a single reason to fail.
  • Use descriptive test names that explain the expected behavior.
  • Mock external dependencies (databases, APIs, file system) to keep tests fast and deterministic.
  • Aim for high coverage of business logic — getters and setters do not need unit tests.

Tools We Use

  • Python: pytest with pytest-mock for mocking.
  • Go: Standard library testing package with testify for assertions.
  • TypeScript: Vitest or Jest with React Testing Library for component tests.

Integration Tests

Integration tests verify that multiple components work together correctly. Unlike unit tests, they use real dependencies (databases, caches, queues) to test actual interactions. Docker Compose is invaluable for spinning up test dependencies.

  • Test API endpoints with a real database — verify that the ORM queries are correct.
  • Test message consumers with a real message broker.
  • Test authentication and authorization flows end-to-end within the service.
  • Use database transactions or test databases that reset between tests.

End-to-End (E2E) Tests

E2E tests verify complete user workflows through the actual UI. They catch integration issues between frontend and backend that other test types miss. However, they are slow, flaky, and expensive to maintain. Use them sparingly for critical user journeys:

  • User registration and login flow.
  • Core transaction workflows (placing an order, creating a shipment).
  • Payment processing.

We use Playwright for E2E testing, which provides cross-browser support, reliable waiting mechanisms, and excellent developer tools for debugging failures.

Testing in CI/CD

Tests are most valuable when they run automatically on every code change:

  • Run unit tests on every push — they should complete in under a minute.
  • Run integration tests on pull request creation — they should complete in under five minutes.
  • Run E2E tests before deployment to staging — they may take 10-15 minutes.
  • Fail builds on test failures — do not deploy code that breaks tests.

Property-Based Testing

Property-based testing generates random inputs and verifies that properties hold for all inputs. Instead of testing "add(2, 3) returns 5", test "add(a, b) always equals add(b, a)" with thousands of random values. This approach catches edge cases that example-based tests miss. Libraries like Hypothesis (Python) and fast-check (TypeScript) make this practical.

Conclusion

A good testing strategy is not about maximizing coverage percentages — it is about maximizing confidence that your software works correctly while minimizing the cost of maintaining tests. Follow the testing pyramid, invest in fast unit tests for business logic, use integration tests for component interactions, and reserve E2E tests for critical user journeys.

Need help with testing strategy? Our engineering team can audit your testing practices.