DEV Community

Cover image for Table-Driven Testing in Typescript
Samuel Ko
Samuel Ko

Posted on

Table-Driven Testing in Typescript

Introduction

Table-Driven Testing, also known as Data-Driven Testing and Parameterized Test, is a popular methodology in Golang.

The idea is to run several sets of input data against the same logic, in order to increase test coverage. πŸ’‘

Let's look at an example in Typescript.

Example

The unit test:

describe('temperature converter', () => {
  it.each`
    celcius | fahrenheit
    ${-100} | ${-148}
    ${0}    | ${32}
    ${100}  | ${212}
  `(
    'converts $celcius degrees C to $fahrenheit degrees F',
    ({ celcius, fahrenheit }: { celcius: number; fahrenheit: number }) => {
      const result = convertCToF(celcius);
      expect(result).toBe(fahrenheit);
    }
  );
});
Enter fullscreen mode Exit fullscreen mode

The test result:
Screenshot of test result

Explanation

The test utilises Tagged Templates to run several sets of arguments against the same function.

If you're using VS Code, you can install Prettier to format the table for you. πŸš€

Benefits of Table-Driven Testing

1. Reduce repetition

Table-Driven Testing not only reduces LOC in unit tests, but it also saves us from writing test descriptions for each of the test cases.

2. Enhance visibility

Table-Driven Testing groups the data at the top, enabling us to compare against the acceptance criteria. It also helps us to identify edge-case scenarios. πŸ”

3. Enable integration with real-life data

It's possible to supply an array of data to it.each. It means we could potentially create an automated system that commit sample production data as a json file to the repository for unit testing purpose.

Below is an example of it.each using an array as input:

describe('temperature converter', () => {
  const testCases = [
    { celcius: -100, fahrenheit: -148 },
    { celcius: 0, fahrenheit: 32 },
    { celcius: 100, fahrenheit: 212 },
  ];

  it.each(testCases)(
    'converts $celcius degrees C to $fahrenheit degrees F',
    ({ celcius, fahrenheit }: { celcius: number; fahrenheit: number }) => {
      const result = convertCToF(celcius);
      expect(result).toBe(fahrenheit);
    }
  );
});
Enter fullscreen mode Exit fullscreen mode

Limitations of Table-Driven Testing

1. Harder to debug

When one of the test cases failed in the table, we would need to comment out other test cases to debug.

2. Not as descriptive

A test case should be validating one particular scenario, e.g. positive value, negative value etc.

Without a meaningful description, it can be hard to understand the purpose of each test case.

To address that, we can add a text description column.

3. The table becomes too big

When a table has 4 columns of input and 1 column of output, it can become harder to understand the behaviour by purely reviewing the unit test. 😡

In that case, consider using smaller tables to describe individual behaviours, or refactor the function to receive less inputs.

A Bad Example

When we start using Table-Driven Testing, it's tempting to put every possible outcome into one big table.

And worse still, we may want to put conditionals into the test script. ❌

Consider the test below:

describe('temperature converter', () => {
  it.each`
    celcius | fahrenheit | error
    ${-300} | ${null}    | ${'celcius cannot be below 273.15'}
    ${-100} | ${-148}    | ${null}
    ${0}    | ${32}      | ${null}
    ${100}  | ${212}     | ${null}
  `(
    'converts $celcius degrees C to $fahrenheit degrees F or throws error $error',
    ({ celcius, fahrenheit, error }: { celcius: number; fahrenheit?: number; error?: string }) => {
      if (!!error) {
        expect(() => {
          convertCToF(celcius);
        }).toThrow(error);
      } else {
        const result = convertCToF(celcius);
        expect(result).toBe(fahrenheit);
      }
    }
  );
});
Enter fullscreen mode Exit fullscreen mode

It has a few issues:

  1. The table has holes. It means we have to consider null when writing the test.
  2. if we need to debug a test case, we would need to determine which code path it takes within the unit test first.
  3. The test script becomes much harder to read.

The better solution is to write a separate test for throws error if celcius is below 273.15.

Conclusion

Table-Driven Testing is a useful tool to increase test coverage. If used wisely, it can improve readability as well.


Thank you for reading.
Hope you find this post interesting!

Have you used table-driven testing before?
Share your experience in the comments!

Top comments (0)

Sentry image

See why 4M developers consider Sentry, β€œnot bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

πŸ‘‹ Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay