vitest and msw testing setup

It's time to reap the full benefits of services, layers and dependency injection.

We want to test our app. Since everything is composable we can create a setup specific for testing with its own Config configuration.

Our objective is to verify that getPokemon performs the correct request and returns the expected response. However, we don't want to make real requests to pokeapi.co, but instead use a local mock server (localhost).

Let's start by setting up our testing environment using vitest and msw:

  • vitest: Testing framework
  • msw: API mocking

Setup vitest and msw for testing

Let's start by installing them both:

pnpm install -D vitest msw

We use msw to intercept and mock the response of HTTP requests. Instead of sending a request to a real server (https://pokeapi.co), msw intercepts requests and returns mock data, with the following benefits:

  • Test app without interacting with any external service
  • Verify correct behavior for all possible responses

msw allows creating handlers that intercept a specific HTTP request and instead return a mocked response.

Let's create a new test folder that contains handlers.ts:

test/handlers.ts
import { HttpResponse, http } from "msw";
import type { Pokemon } from "../src/schemas";

const mockPokemon: Pokemon = {
  id: 1,
  height: 10,
  weight: 10,
  order: 1,
  name: "myname",
};

export const handlers = [
  // 👇 Intercept requests for any pokemon (using wildcard `*`)
  http.get("http://localhost:3000/api/v2/pokemon/*", () => {
    return HttpResponse.json(mockPokemon);
  }),
];

handlers is a list of requests that are intercepted by msw. Instead of making the full request msw will return the mock response we defined (mockPokemon).

Setup up testing with node

The last step of this setup is creating a testing server for node.

We create a new test/node.ts file that exports the result of calling setupServer from msw/node with our custom handlers:

test/node.ts
import { setupServer } from "msw/node";
import { handlers } from "./handlers";

export const server = setupServer(...handlers);

msw requires to open, reset and close the server we defined above. We can do this inside beforeAll, afterEach and afterAll in a new index.test.ts file:

index.test.ts
import { afterAll, afterEach, beforeAll } from "vitest";
import { server } from "../test/node";

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

The final step is adding a test command to run our test with vitest:

package.json
{
  "name": "effect-getting-started-course",
  "version": "1.0.0",
  "main": "src/index.js",
  "author": "Sandro Maglione",
  "license": "MIT",
  "scripts": {
    "dev": "BASE_URL=https://pokeapi.co tsx src/index.ts",
    "test": "vitest"
  },
  "devDependencies": {
    "@types/node": "^20.14.10",
    "msw": "^2.3.1",
    "tsx": "^4.16.2",
    "typescript": "^5.5.3",
    "vitest": "^2.0.2"
  },
  "dependencies": {
    "@effect/platform": "^0.69.2",
    "effect": "^3.10.0"
  }
}