DEV Community

Cover image for Using Jest to bulletproof a TypeScript Class - Part 1
João Textor
João Textor

Posted on

Using Jest to bulletproof a TypeScript Class - Part 1

Hello, my fellow developers.

In my previous post, I wrote about how to develop a simple and easy tool for generating keys for you to use in your projects.

As I wanted to publish it as a package on npmjs.com, I needed to write some tests to ensure everything was running smoothly in every situation.

Picture this: You've spent some time crafting a TypeScript library, meticulously designing its features. However, before you unleash your creation upon the world, you need to ensure that it's as robust as a tank and as reliable as your grandma's secret cookie recipe, right?

So I chose Jest.js, the trusty sidekick of TypeScript developers, ready to assist in the quest for bulletproof code.

In this post, we're going to start crafting a series of unit tests using Jest.js to ensure our Key Generator tool behaves impeccably.

I decided to split this into a 2-part article because it ended up being too long.

Testing the validateGroupFormat method

Our first group of tests will focus on our private method validateGroupFormat, guaranteeing it throw the errors when needed.

Let's start by setting the stage for our tests by writing 2 describes, thus creating 2 context blocks for our actual tests.

import CodeGenerator, { ICodeGenerator } from "../index";

describe("Key Generator Tests", () => {
  describe("Testing 'validateGroupFormat' errors and warnings'", () => {
  let sut: ICodeGenerator;

  // Our tests will be written here.
  [...]

  });
});

Enter fullscreen mode Exit fullscreen mode

Don't forget to import our dependencies: the CodeGenerator class and our interface ICodeGenerator, since our sut (system under test) should have this type.

Also, we are not going to call the actual validateGroupFormat, but test its functionality by actual calling our generate method.

Now, let's see our first test.

Empty groupFormat

When a developer uses the CodeGenerator class without specifying the groupFormat, we don't want to pester them with unnecessary validation checks. Instead, we choose to be polite and silently skip the validation process.

it("Should skip validation if 'groupFormat' was not informed by the user", () => {
    sut = new CodeGenerator(5);
    const consoleSpy = jest.spyOn(console, "log");
    sut.generate();
    expect(consoleSpy).toHaveBeenCalledWith("Group Format is not defined, skipping validation.");
});
Enter fullscreen mode Exit fullscreen mode

If you don't remember or didn't read my last post, I suggest you take a quick look at it by clicking here so you can get familiar with the code being tested.

So, when the groupFormat is empty this validation will simply call a console.log with the message 'Group Format is not defined, skipping validation.'.

Therefore, we used the 'spyOn' method on Jest to watch if the "log" method of the console was being called when we generate a new code.

You may be wondering why we even called our validateGroupFormat auxiliary method in the first place if the user did not inform the groupFormat property.

Well, we did this to comply with the first principle of SOLID: single responsibility. We could have actually checked if the groupFormat was empty before calling the validation method, but that would mean the generate method would also be doing its own validations, and this can get confusing. Of course this is a very small project, but it's important to follow a clean code pattern even when we are working on small pieces of code, so others (and your future self) can properly understand what the code is doing.

With that clear, let's jump to the other test.

groupFormat being passed with the wrong type of characterType

The groupFormat purpose is to let the user customize the order of characters (Letters or Numbers) in the output key.

it("Should throw an error when 'groupFormat' is used with 'characterType' other than 'LettersAndNumbers'", () => { 
    sut = new CodeGenerator(5, { characterType: "Letters", groupFormat: "NNLNL", }); 
    expect(() => sut.generate()).toThrow(
        new Error("The groupFormat can only be used with 'LettersAndNumbers' characterType!"), 
    ); 
});
Enter fullscreen mode Exit fullscreen mode

So, in our second test, we don our detective hats to catch developers trying to mix incompatible options. Thus, we test by passing "Letters" to characterType property and at the same time we configure our output to have a groupFormat of "NNLNL".

The test is pretty simple. It ensures that our code enforces rules consistently, preventing developers from using incompatible settings that could lead to unexpected behavior.

One thing to notice here is that we have called sut.generate inside a anonymous function. If you don't do that, the test will not run due the Error being actually thrown.

groupFormat contains invalid characters

The third and final test for our validation method is all about maintaining order on our key generation tool. We won't tolerate any rogue characters sneaking into the groupFormat. If a developer dares to include characters other than 'L' (for letters) or 'N' (for numbers), our code will not hesitate to raise the alarm.

The code for our test is the following:

it("Should throw error when 'groupFormat' has any characters other than 'L' or 'N'", () => {
    sut = new CodeGenerator(5, {
    characterType: "LettersAndNumbers",
    groupFormat: "LLLLB",
    });
    expect(() => sut.generate()).toThrow(
    new Error("The group format can only contain letters 'L' and numbers 'N'"),
    );
});
Enter fullscreen mode Exit fullscreen mode

The validation here is similar to the one being made in the previous test.
Our validation method is using a RegEx statement to match our string, as seen in the previous post.

Here we passed the string "LLLLB", so our code successfully throws an error when trying to generate a key.

generate method

To void making this post too long, I will place only the first test for the generate method. The second part is going to address the rest of the tests.

Generating a random key with the default settings

What a better way of starting our tests for the generate method if not by actual generating a key, right?

We are going to start with the default settings (no props being passed).

it("Should generate a random code with the default options", () => {
    sut = new CodeGenerator(5);
    const randomSpy = jest.spyOn(Math, "random");
    const code = sut.generate();

    expect(randomSpy).toHaveBeenCalledTimes(code[0]!.length);
    expect(code).toHaveLength(1);
    expect(code[0]).toHaveLength(5);
});
Enter fullscreen mode Exit fullscreen mode

Here we are asking our "System Under Test" to generate a 5-character key.
We're also using Jest's spyOn to watch on the random method of the Math library.

Our code, which is an Array, should have a length of 1 and the length of the key needs to be of 5 characters. Hence, logic requires that the random method is being called 5 times as well.

In the next post, which is going live a few hours after this one, we are going to address the rest of our tests.

Hope y'all enjoyed this first part.
I'll be glad to read your thoughts.

Top comments (0)