Testing Troubles with Jest & ESM and how to fix it

bregwin

Bregwin Jogi

Posted on November 9, 2024

Testing Troubles with Jest & ESM and how to fix it

This week, I was tasked with adding tests to my RefactorCode project. As I started adding new features, I noticed that some existing features were breaking, and it became increasingly difficult to test everything manually. This made it clear that I needed to implement proper automated testing to ensure the application is stable.

Since I had prior experience with Jest from my previous Cloud Computing for Programmers course, where I worked with Node.js, I decided to use it for this project as well. I started by writing some tests to check the functionality of my code. However, I encountered a few errors that I hadn’t faced before. After some debugging and searching through Stack Overflow, I realized that there are additional configurations required when using ESM (ECMAScript Modules) with Jest. In my previous project, I had used CommonJS, which worked perfectly. While I could have opted to use Babel for the conversion, Jest offered a new beta feature that allowed ESM to run directly. I decided to give it a try, and it worked great!

This explains everything related to configuring jest with ESM: https://jestjs.io/docs/ecmascript-modules

Here is a quick overview of the setup:

Install Jest:

npm install --save-dev jest
Enter fullscreen mode Exit fullscreen mode

Create a jest.config.js file. Here I set what folders to ignore as well:

export default {
  testPathIgnorePatterns: ["/node_modules/", "/examples"],
  transform: {},
};
Enter fullscreen mode Exit fullscreen mode

In the package.json scripts section, use the experimental argument for jest to work with ESM modules:

"scripts": {
    "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
}
Enter fullscreen mode Exit fullscreen mode

Create a test file. I created all the tests within a test folder in root directory. Here is a simple one I created:

import { readFile, checkIfDirectory } from "../src/fileUtils";

describe("File Utility", () => {
    test("Read File", () => {
      readFile("./examples/test.txt").then((data) => {
        expect(data).toBe("Hello World");
      });

    });

    test('should return true if the path is a directory', async () => {
      const result = await checkIfDirectory('./examples');
      expect(result).toBe(true);
    });
});
Enter fullscreen mode Exit fullscreen mode

If you want to test using a single test file:

npm test -- banner.test.js
Enter fullscreen mode Exit fullscreen mode

For running all the tests, we use the script, we added earlier in package.json:

npm run test
Enter fullscreen mode Exit fullscreen mode

Finally, the basic tests were set up. However, this was just the beginning of my troubleshooting journey. I also faced difficulties with mocking libraries and modules, especially since they were using ESM. These required extra configuration, but after some tweaking, I was able to get everything working, and the tests ran successfully.

Instead of using the regular jest.mock, you have to use jest.unstable_mockModule:

For example: await jest.unstable_mockModule("fs", () => ({
  existsSync: jest.fn(),
  readFileSync: jest.fn(),
}));
Enter fullscreen mode Exit fullscreen mode

See this section for more info.

One of the core functionalities I wanted to test involved integrating with the Gemini API. Since I didn’t want to rely on the live API for testing, I decided to mock the API calls. Initially, I tried using Nock, but I ran into issues because it didn’t work well with Node’s default fetch implementation. It seemed that the Gemini library was using the default fetch instead of a third-party fetch implementation, which caused the mock to fail. After several attempts without success, I switched to another library called MSW (Mock Service Worker). Although it required a bit more setup to create a mock server, it worked flawlessly on the first try. I crafted an example response based on how the Gemini API would respond, which allowed me to test the refactored functionality.

Here is how the mock server was set up for reference:

import { http, HttpResponse } from 'msw'

export const handlers = [
    http.post('https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent', () => {
        return HttpResponse.json({
                candidates: [
                    {
                        content: {
                            parts: [
                                {
                                    text: JSON.stringify({
                                        explanation: "The provided code has been refactored to address several issues and improve readability.",
                                        refactoredCode: `
                                            var count = 0;
                                            var message = "Hello";
                                        `
                                    })
                                }
                            ],
                            role: "model"
                        },
                        finishReason: "STOP",
                        index: 0
                    }
                ],
        });
    }),    
];

Enter fullscreen mode Exit fullscreen mode

You can see that I am mocking a specific API endpoint that I am using for the application. Now all you have to do this add the below to start the server while testing:

import { setupServer } from "msw/node";
import { handlers } from "../mocks/handler.js";
export const server = setupServer(...handlers);

server.listen();
Enter fullscreen mode Exit fullscreen mode

Reflecting on the process, this was a great learning experience for me. In hindsight, I probably should have checked the compatibility of the libraries I was using and considered any potential issues beforehand. Despite the challenges, I’m glad to have reached a point where my tests are running smoothly. I look forward to adding more tests in the future and improving the overall stability of my project.

💖 💪 🙅 🚩
bregwin
Bregwin Jogi

Posted on November 9, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related