DEV Community

Kannav Sethi
Kannav Sethi

Posted on

Testing with Jest

A Guide to Testing in TypeScript Projects: Overcoming Challenges with Jest and Bun

In this blog post, I’ll walk you through my experience setting up and using Jest for testing in a TypeScript project. Along the way, I’ll share the hurdles I faced, particularly when trying to use Jest with Bun (my preferred package manager), and how I handled mocking LLM (Large Language Model) responses.

Again I will be demoing this on my project, DialectMorph

Using Jest

Framework

When it comes to testing, Jest is one of the most popular frameworks for JavaScript and TypeScript projects. I chose Jest because:

  • Ease of Setup: Jest works out of the box with minimal configuration for most projects.
  • Built-in Mocks and Spies: Jest has built-in support for mocking and spying, which makes it easy to isolate parts of your code, especially when working with external dependencies.
  • Snapshot Testing: Jest provides the ability to perform snapshot testing, useful when dealing with complex objects.
  • Active Ecosystem and Community: With a large community and lots of plugins, Jest offers solid support and continuous improvements.

For more information, you can visit Jest’s official documentation: Jest.

Bun: The Package Manager

I use Bun as my package manager for managing dependencies in my TypeScript projects. It is known for its speed and simplicity. However, I encountered a significant issue with Bun and Jest compatibility that I’ll elaborate on later. You can learn more about Bun at Bun.

Setting Up the Project

Initial Setup

To get started, I first installed Jest, TypeScript, and related dependencies:

bun add -d jest ts-jest @types/jest
Enter fullscreen mode Exit fullscreen mode

Adding Bun to the Mix

Since I use Bun as my package manager, I ran into issues while trying to use Bun with Jest, as Bun doesn't natively support Jest out-of-the-box. This led to several frustrating days of trying to resolve compatibility problems. After many attempts at using bun test for Jest, I eventually had to work around it by running Jest directly using npm(even though Bun was the default package manager).

I used Bun for dependency management and npm for testing. This workaround helped resolve the Jest compatibility issue but resulted in additional setup time and confusion.

bun install
npm test
Enter fullscreen mode Exit fullscreen mode

Mocking LLM Responses

One of the key challenges in testing is handling external dependencies, especially when dealing with large language models (LLMs) or API calls. In my project, I needed mock responses from LLMs interacting with the file system.

To achieve this, I used Jest’s powerful mocking abilities. I mocked several modules (fs, os, path, and toml) to isolate the functions under test. For example:

jest.mock("fs");
jest.mock("os");
jest.mock("path");
jest.mock("toml");
Enter fullscreen mode Exit fullscreen mode

This allows me to simulate different scenarios without actually interacting with the file system or external dependencies.

Writing Test Cases

File Utility Functions

I had a set of utility functions to test, including file operations like creating directories and files. Here's how I structured the test cases for one of the functions, makeDir:

describe("makeDir Function", () => {
  it("Creates a Directory and returns its path", () => {
    const result = makeDir("testDir");
    expect(fs.mkdirSync).toHaveBeenCalledWith("/root/testDir", { recursive: true });
    expect(result).toBe("/root/testDir");
  });

  it("Doesn't create directory if it already exists", () => {
    (fs.existsSync as jest.Mock).mockReturnValue(true);
    const result = makeDir("testDir");
    expect(result).toBe("/root/testDir");
    expect(fs.mkdirSync).not.toHaveBeenCalled();
  });
});
Enter fullscreen mode Exit fullscreen mode

Each of the utility functions (makeDir, createFile, extractCodeBlock, loadTomlConfigFile) had corresponding test cases that mocked the necessary parts of the code (like file reading and writing) to avoid side effects.

What I Learned from Writing Test Cases

Writing test cases helped me discover several interesting things:

  • Mocking complexity: Mocking certain modules, especially system-level ones like fs, os, and path, proved tricky. However, Jest’s jest.mock function made this easier once I figured out how to properly mock these external dependencies.
  • Mocking non-trivial dependencies: One of the most challenging parts of mocking was the toml parsing, as it involved simulating both file reading and parsing logic. But with proper mocks, I could easily simulate various file contents.
  • Test organization: I found that grouping related tests together (as seen in the nested describe blocks) kept my tests organized and made them easier to read and maintain.

Issues

  • Jest and Bun compatibility issues: The biggest obstacle I encountered was integrating Jest with Bun. This issue caused a lot of wasted time, and I had to use npm instead of Bun for running tests. This was a frustrating experience and a learning moment about the importance of checking compatibility early in the process.
  • Mocking non-obvious behaviors: Another moment of realization was learning how to mock file system behaviors, especially with Jest's ability to mock implementation details like file reads and writes. This was crucial to avoid side effects in tests.

Uncovered Bugs and Edge Cases

While writing tests, I discovered some edge cases in the file utility functions. For instance:

  • Handling non-existent files: There were scenarios where the code didn't handle the absence of files correctly. By testing this behavior, I was able to ensure that the code didn’t break and returned expected results.
  • Unexpected behaviors with path resolution: By mocking the path module, I ensured that the directory and file paths were correctly resolved across different environments.

Conclusion

This process has been an eye-opener in terms of testing and mocking in JavaScript/TypeScript. Prior to this project, I hadn’t done much testing, and it was rewarding to see how structured testing could catch bugs and ensure code correctness.

I believe personally, I would rather avoid writing test cases at least after most of the code is written as it gets too complicated, for me If I know I have to write tests, I would rather begin with a TDD approach

Top comments (0)