Reliable Test Suites

In this latest blog from the developers on our Product Traction team, they outline their best practices for writing less brittle test suites.

Making Tests Less Brittle: Best Practices for Reliable Test Suites

Writing robust, reliable tests is a fundamental part of delivering quality software. However, brittle tests—those that break easily due to minor code changes—can slow down development and make it harder to confidently release new features. Below, we'll dive into some techniques you can use to make your tests less brittle, especially when working with Prisma, mocks, and time-sensitive data like dates and time zones.

1. Minimizing Brittle Tests with Prisma

When working with an ORM like Prisma, it's essential to be mindful of how much data you’re testing. The key here is simplicity: only include the parameters that are needed for each test.

Why it Matters:

When you over-specify parameters or try to cover too much ground, you increase the likelihood that your test will break as the codebase evolves. If the Prisma schema changes—adding, removing, or modifying fields—overly verbose tests will fail, even if the specific feature being tested hasn’t changed.

Tip:

In your expect statements, focus only on the specific parameter under test. For example, if you're testing a user's name retrieval, don’t also assert against their email, role, or other attributes unless it's crucial to the test. This will help your tests remain resilient even when the underlying database schema evolves.

expect(result.name).toBe("John Doe");

// instead of:

expect(result).toEqual({

    id: 1,

    name: "John Doe",

    email: "john.doe@example.com",

    role: "admin"

});

In the above example, if we were to then add another parameter to the result object such as state: “enabled” then the second test will fail while the first one will still pass. In some cases you do want to confirm that you are getting the entire state back, but in many cases you may only care about the single parameter under test, in which case adding a parameter to the result object could result in many failing tests that need to be fixed that could be passing if they were designed more precisely.

// still passes:

expect(result.name).toBe("John Doe");

// now fails:

// missing {state: "enabled"}

expect(result).toEqual({

    id: 1,

    name: "John Doe",

    email: "john.doe@example.com",

    role: "admin"

});

2. Single Responsibility Tests

A common source of test brittleness is trying to do too much in a single test. Each test should be focused on a single responsibility—that is, it should test exactly one behaviour or piece of functionality.

Why it Matters:

When a test tries to cover multiple unrelated functionalities, it becomes more susceptible to breakage. If one feature changes, the entire test may fail even though the rest of the test is still valid. This can lead to wasted debugging effort and confusion, making it harder to pinpoint the real issue.

Tip:

Write focused tests. If you're testing an API endpoint, don’t try to test the full request-response lifecycle and database interaction all in one go. Break tests into separate units: one for the API response format, another for the data integrity, and so on.

// Test 1: Validates the API response status code

expect(response.status).toBe(200);

// Test 2: Verifies the specific data in the API response

expect(response.body.user.name).toBe("John Doe");

3. Proper Mocking for External Dependencies

Mocking external services or modules is a powerful way to avoid flaky tests. However, improper or over-reliant mocking can lead to brittle tests that don’t properly validate the code’s behavior.

Why it Matters:

Mocks should simulate external dependencies only when necessary. Over-mocking can give you a false sense of security—your tests might pass because the mock always behaves in a controlled way, even when the real-world interaction might fail or behave differently.

Tip:

When mocking, focus on the minimal external interaction that’s critical to the test. Use libraries like jest.mock or sinon to create mocks that are simple and controlled but avoid over-complicating them with unnecessary logic.

jest.mock('axios');

axios.get.mockResolvedValue({ data: { user: 'John Doe' } });

4. Handling Date and Time in Tests

Tests that deal with dates and times can be particularly brittle due to issues like Daylight Saving Time (DST) changes and varying time zones. Hardcoding specific dates or assuming that "now" will always be consistent across test runs is a recipe for flaky tests.

Why it Matters:

Date and time handling needs special care to ensure your tests don't unexpectedly fail due to environmental factors like timezone differences between your local machine and CI pipeline, or due to DST transitions.

Tip:

Use libraries like MockDate or built-in tools in testing libraries to mock the current date and time. This makes your tests deterministic, as they won’t rely on the actual current time or date during execution. Always normalize the timezone and account for DST changes to avoid surprises.

const MockDate = require('mockdate');

// Set a fixed date for consistency

MockDate.set('2024-01-01T00:00:00Z');

// Test runs with the fixed date

expect(getCurrentYear()).toBe(2024);

// Reset to the real date after the test

MockDate.reset();

Writing less brittle tests requires discipline and attention to detail, but the payoff is immense. By ensuring your tests are narrowly focused, properly mocked, and handle time-based data in a deterministic way, you'll not only reduce unnecessary test failures but also build confidence in your test suite's ability to catch real issues.

Brittle tests can slow down development, increase frustration, and lead to false negatives. These downsides, especially frustration, can lead to developers writing fewer tests in the future. By following the practices above, you’ll be on your way to creating a robust, maintainable test suite that grows with your application—without breaking at every minor change.


The Thin Air Labs Product Traction team provides strategic product, design and development services for companies of all sizes, with a specific focus on team extensions where they seamlessly integrate into an existing team. Whether they are deployed as a team extension or as an independent unit, they always work with a Founder-First Mindset to ensure their clients receive the support they need.

To learn more about our Product Traction service, go here.

Build what's next with us