Test costs and maintainability

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

This lesson preview is part of the Pain Free Mocking with Jest course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to Pain Free Mocking with Jest, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

Thumbnail for the \newline course Pain Free Mocking with Jest
  • [00:00 - 00:11] As test suites grow over time, maintainability becomes crucial to avoid technical depth. Let's discuss some patterns for keeping tests clean, compact and avoiding unnecessary costs.

    [00:12 - 00:21] One common problem is duplication between test setups. We can drastically reduce this flow by centralizing logic in just life cycle hooks like before each or after each.

    [00:22 - 00:33] But sometimes just life cycle hooks grow too large, capturing logic across a fire. When the fire becomes big, we often have to scroll to find the before each hook and then back to the test case.

    [00:34 - 00:45] This inconvenience is often referred to as scrolling fatigue. A common pattern of practice to keep tests readable when we have blue-ted life cycle hooks is to extract common setup code into small factory functions.

    [00:46 - 00:52] Using small factory functions, we can avoid scrolling fatigue to find relevant hook code. Let's see an example use case.

    [00:53 - 01:03] In this describe block, I have two test cases. And in each of the test case in the act statement, we create user object DB user, mock response and mock request.

    [01:04 - 01:10] This is the same in the second test. We create the user object DB user, mock request and mock response.

    [01:11 - 01:28] We need to remove this duplication because this will increase the cost of maintaining our tests. In this other implementation of the same test suite, we have created small factory functions to create user object, the create DB user object, create mock and create mock with JSON response.

    [01:29 - 01:37] And if we look at the orange step, we can see that we made use of the small factory functions here. Create user, create DB user and create mock.

    [01:38 - 01:45] In the second test case, we did the same in the arrange setup. Create user, create DB user and create mock with JSON response.

    [01:46 - 02:05] We should also strategically choose which type of test to prioritize when constrained on time or resources. Integration tests generally provide better reliability guarantees due to their wider scope. So when first to peak, choose integration tests over unit tests. Proper abstraction and organization is key as test suites evolve.

    [02:06 - 02:21] By removing duplication and focusing tests where they matter most, we maximize value while minimizing costs long term. Clean tests are happier to work with and easier to refactor as needs change. Also, testing can get tricky if we don't follow some simple guidelines.

    [02:22 - 03:13] Let me share two important best practices. First, test should generally have no logic beyond simple validation. Avoid putting things like try catch blocks or if else conditioners directly in test code. If tests lean and focused only on expected flow, program logic should stay in the code on the test, not mixed with. Assessions in the test file. This makes test easier to understand at a glance. The only exception to this rule in my opinion is when you write helper or factory functions that set up scenarios for testing. The second best practice I would like to share is always use strict assertions wherever possible rather than lose validations. Instead of checking that the value is greater than 1, be more specific and assert that it is 2. Lose checks that can mask edge cases that might fail down the road. Following these practices, resulting tests that are clean, self-decommenting and less prone to brittle issues as code evolves over time.