Using Jest to bulletproof a TypeScript Class - Part 1
Tags: webdev, testing, javascript, jest
Published: 2023-09-26 19:00:00

Hello, my fellow developers.

In my [previous post](https://dev.to/joaotextor/how-to-write-a-key-generator-in-typescript-3049), 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 `describe`s, thus creating 2 context blocks for our actual tests.

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

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

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

  });
});

```
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.

```js
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.");
});
```

If you don't remember or didn't read my last post, I suggest you take a quick look at it by [clicking here](https://dev.to/joaotextor/how-to-write-a-key-generator-in-typescript-3049) 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.

```js
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!"), 
	); 
});
```

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:

```js
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'"),
	);
});
```

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).

```js
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);
});
```

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.