DEV Community

Cover image for Enhancing Test Clarity and Diagnostics in Abstractions
Vinicius Blazius Goulart for Woovi

Posted on

Enhancing Test Clarity and Diagnostics in Abstractions

At Woovi, we strongly believe in maintaining high testing standards as an important part of our development approach. We make sure to include tests along with our features, and by using different simplifications, we ensure that we can quickly and safely put these tests together.

For both our front-end and back-end systems, we mainly use Jest as our primary testing tool. This flexible tool helps us thoroughly check how well our software parts work. Furthermore, we've created specific functions for each environment that make our testing process smoother.

The Importance of Error Stack Traces

The error stack trace is a crucial tool for diagnosing issues in our development work. It gives us valuable information about why failures happen and helps us find ways to fix them. For example:

it("should sum two numbers", async () => {
  expect(1 + 2).toBe(4);
});
Enter fullscreen mode Exit fullscreen mode

In this basic example, we intentionally make a mistake. The error message that comes up won't just show that something went wrong, but it will also show exactly where the mistake happened. This makes it easier to figure out and fix the problem.

sum-test

Introducing Abstractions

To demonstrate the utility of abstractions in testing, consider the following code segment:

const assertSum = (numbers: number[], result: number) => {
  const [x, y] = numbers;
  expect(x + y).toBe(result);
};

it("should sum two numbers", async () => {
  assertSum([1, 2], 4);
});
Enter fullscreen mode Exit fullscreen mode

In this case, we've put the test steps inside the assertSum function, which makes it easier to reuse the code. If something goes wrong, the error message it gives will be like this:

sum-test-2

At first glance, it looks like a useful message, it's easy to find which test broke since we only have one.

Navigating Complexity

In real-world scenarios, our codebases encompass multiple tests and numerous operations. For example:

const assertSum = (numbers: number[], result: number) => {
  const [x, y] = numbers;
  expect(x + y).toBe(result);
};

it("should sum two numbers", async () => {
  assertSum([1, 2], 3);
  assertSum([1, 6], 7);
  assertSum([9, 2], 4);
  assertSum([4, 6], 12);
});

it("should sum two numbers with two decimal places", async () => {
  assertSum([12, 21], 89);
  assertSum([16, 65], 80);
  assertSum([91, 20], 111);
  assertSum([40, 62], 102);
});
Enter fullscreen mode Exit fullscreen mode

sum-test-3

In these complex situations, doesn't this message tell us exactly where it broke, at which assertSum did it break? in which line? in a scenario closer to reality, you use your abstractions in several of your tests, and failing with that message only makes the situation worse, you don't know for sure which line failed, which of the operations failed.

Enhancing Error Diagnostics

To make our abstractions even better at diagnosing issues, we use a try-catch block:

const assertSum = (numbers: number[], result: number) => {
  const [x, y] = numbers;

  try {
    expect(x + y).toBe(result);
  } catch (error) {
    Error.captureStackTrace(error as Error, assertSum);
    throw error;
  }
};
Enter fullscreen mode Exit fullscreen mode

This is the output:

sum-test-4

This improvement makes the error stack trace more detailed, giving a complete picture of where the error happened, what was expected, and what actually occurred. This leads to a thorough diagnostic output that helps developers quickly solve the problem.

Why to abstract the expect

You can have an abstraction with expect in cases where you will always check the same data.

A more realistic example would be where you mock a mutation operation and ensure that the name of the next operation to be executed is the name you expect.

export const assertOperation = <T extends OperationType>(
  environment: RelayMockEnvironment,
  name: string,
) => {
  const queryOperation = environment.mock.getMostRecentOperation();

  const operationName = queryOperation.root.node.name;

  try {
    expect(operationName).toBe(name);
  } catch (error) {
    Error.captureStackTrace(error as Error, assertOperation);

    throw error;
  }
};
Enter fullscreen mode Exit fullscreen mode

In Conclusion

At Woovi, our approach to testing focuses on being thorough, using abstractions, and being precise in diagnosing issues. With the help of Jest and carefully designed abstractions, we enable our development teams to create strong and top-notch software that not only meets but consistently goes beyond
expectations.


Woovi is a Startup that enables shoppers to pay as they please. To make this possible, Woovi provides instant payment solutions for merchants to accept orders.


Photo by David Travis on Unsplash

Top comments (0)