hono icon indicating copy to clipboard operation
hono copied to clipboard

Running vitest fails after creating a Hono Cloudflare project

Open callmetwan opened this issue 1 year ago • 12 comments

What version of Hono are you using?

4.2.4

What runtime/platform is your app running on?

Cloudflare Workers

What steps can reproduce the bug?

Repo for reproduction: https://github.com/callmetwan/hono-cloudflare-pages-vitest

npm create hono@latest hono-cloudflare-pages-vitest
Need to install the following packages:
[email protected]
Ok to proceed? (y) y
create-hono version 0.6.3
✔ Using target directory … hono-cloudflare-pages-vitest
? Which template do you want to use? cloudflare-pages
✔ Cloning the template
? Do you want to install project dependencies? yes
? Which package manager do you want to use? yarn
✔ Installing project dependencies
🎉 Copied project files
Get started with: cd hono-cloudflare-pages-vitest

Install vitest

yarn add -D vitest

Add to package.json

"scripts: {
 .  .  .
    "test": "vitest"
},

Create a test

// indext.test.tsx
import {test, expect} from "vitest";

test("should pass", () => {
  expect(1 + 1).toBe(2);
})

Run tests

yarn test

Failure:

 FAIL  src/index.test.tsx [ src/index.test.tsx ]
TypeError: Cannot read properties of undefined (reading 'test')
 ❯ src/index.test.tsx:3:1
      1| import {test, expect} from "vitest";
      2|
      3| test("should pass", () => {
       | ^
      4|   expect(1 + 1).toBe(2);
      5| })

Commenting out the build() plugin in vite.config.ts results in tests passing. Something in the plugin is causing an issue.

What is the expected behavior?

Tests should pass, the test method should be defined

What do you see instead?

 FAIL  src/index.test.tsx [ src/index.test.tsx ]
TypeError: Cannot read properties of undefined (reading 'test')
 ❯ src/index.test.tsx:3:1
      1| import {test, expect} from "vitest";
      2|
      3| test("should pass", () => {
       | ^
      4|   expect(1 + 1).toBe(2);
      5| })

Additional information

Logging the test method in the test file does actually show the the test method is defined at some point:

stdout | src/index.test.tsx:2:9
{
  test: [Function: chain2] {
    each: [Function (anonymous)],
    skipIf: [Function (anonymous)],
    runIf: [Function (anonymous)],
    extend: [Function (anonymous)],
    withContext: [Function (anonymous)],
    setContext: [Function (anonymous)],
    mergeContext: [Function (anonymous)],
    fn: [Function (anonymous)] {
      each: [Function (anonymous)],
      skipIf: [Function (anonymous)],
      runIf: [Function (anonymous)],
      extend: [Function (anonymous)]
    }
  }
}

callmetwan avatar Apr 16 '24 02:04 callmetwan

I'm happy to try and help debug this further, but I am very unfamiliar with the internals of both hono and vitest and would need some guidance on where to start

callmetwan avatar Apr 16 '24 02:04 callmetwan

Hi @callmetwan

How about using this vitest.config.ts?

import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    globals: true
  }
})

yusukebe avatar Apr 16 '24 07:04 yusukebe

Well, that solves it by side stepping vite.config.ts which means the Cloudflare adapter is no longer being loaded. This also works:

// vitest.config.ts
import { defineConfig } from "vitest/config";

export default defineConfig({});

Something about the cloudflare adapter breaks Vite's ability to recognize it's own testing module.

I view this as being important because it means bindings no longer work properly. You could make the argument that you could mock them, but having bindings that provide defaults is something many rely upon and not having access in a unit testing environment is limiting.

callmetwan avatar Apr 17 '24 21:04 callmetwan

@callmetwan

You can use @cloudflare/vitest-pool-workers: https://developers.cloudflare.com/workers/testing/vitest-integration/

yusukebe avatar Apr 17 '24 22:04 yusukebe

Oh wow, I had no idea that existed! Thank you for sharing.

This is definitely the answer, but now I am getting an error that I cannot tell if it is related to Vitest + Cloudflare + Hono or if this is something I have done incorrectly.

I am seeing that context.env is completely undefined. No bindings at all are loaded. I know vitest is loading my wrangler.toml file because @cloudflare/vitest-pool-workers threw an error indicating nodejs_compat needed to be enabled. After enabling the tests ran but context.env is undefined.

TypeError: Cannot read properties of undefined (reading 'd1primary')

I apologize if this has moved more into Cloudflare support; I can't tell if this is a Hono issue or something else. Here is my wrangler file:

wrangler.toml

compatibility_date = "2024-04-03"
compatibility_flags = [ "nodejs_compat" ]

[[d1_databases]]
binding = "d1primary"
database_name = "database" #real value redacted
database_id = "00000000-0000-0000-0000-000000000000" #real value redacted
migrations_dir = "./src/server/migrations"

vitest.config.ts

//vitest.config.ts

import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config";

export default defineWorkersProject({
  test: {
    poolOptions: {
      workers: {
        wrangler: {
          configPath: "./wrangler.toml",
        },
      },
    },
  },
});

Is there anything obvious you see missing?

callmetwan avatar Apr 18 '24 02:04 callmetwan

@callmetwan

How about using this config?

export default defineWorkersProject(async () => {
  return {
    test: {
      poolOptions: {
        workers: {
          miniflare: {
            compatibilityFlags: ['nodejs_compat'],
            compatibilityDate: '2024-04-01',
            d1Databases: ['DB'],
          }
        }
      }
    }
  }
})

yusukebe avatar Apr 19 '24 00:04 yusukebe

@yusukebe Unfortunately context.env is still undefined using that config :/

callmetwan avatar Apr 19 '24 00:04 callmetwan

@callmetwan

Please refer to this project: https://github.com/yusukebe/testing-d1-app-with-types

yusukebe avatar Apr 19 '24 00:04 yusukebe

@yusukebe Thank you for sharing that. I see that repo depicts client testing, it seems to work by hydrating a test client with the env from the cloudflare testing package.

Is there no way to do "backend' testing in the way the Hono docs show?

// index.ts
app.get('/posts', (c) => {
  return c.text('Many posts')
})

app.post('/posts', (c) => {
  return c.json(
    {
      message: 'Created',
    },
    201,
    {
      'X-Custom': 'Thank you',
    }
  )
})

// index.test.ts
describe('Example', () => {
  test('GET /posts', async () => {
    const res = await app.request('/posts')
    expect(res.status).toBe(200)
    expect(await res.text()).toBe('Many posts')
  })
})

callmetwan avatar Apr 19 '24 01:04 callmetwan

That is a test for a backend.

yusukebe avatar Apr 19 '24 01:04 yusukebe

const client = testClient(api, env)

Is this not a test version of hc from hono/client?

Regardless, when I directly build a test against api in the test repo you created, it fails:

// api.test.ts
import api from '../src'

it("this tests with api directly", async () => {
  const response = await api.request("/api/");
    expect(response.status).toBe(200);
})

The output

stderr | test/api.test.ts > this tests with api directly
TypeError: Cannot read properties of undefined (reading 'DB')

Is this expected?

callmetwan avatar Apr 19 '24 01:04 callmetwan

I think I found a solution for what I'm looking for:

import { describe, it } from "vitest";
import { env } from "cloudflare:test";
import app from "./index";

describe("Routing rules", () => {
  it("should have a route for the home page", async ({ expect }) => {
    const req = new Request("/", { method: "GET" });
    const response = await app.fetch(req, env);
    expect(response.status).toBe(200);
    expect(response.body).toContain(`<div id="root">`);
  });
});

This seems to work. For anyone else finding this, you'll need to install @cloudflare/vitest-pool-workers as a dev dependency. env is imported from a module that the vitest-pool-workers package makes available at runtime. See https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/#cloudflaretest-module-definition for more information

callmetwan avatar Apr 20 '24 02:04 callmetwan