test(ai): migrate tests to Vitest - #10363
Conversation
🦋 Changeset detectedLatest commit: 3addc62 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Vertex AI Mock Responses Check
|
There was a problem hiding this comment.
Code Review
This pull request migrates the test suite for the packages/ai package from Karma and Mocha to Vitest, which includes updating package scripts, adding Vitest configuration files, and refactoring several test files to use Vitest's mocking utilities (vi.mock, vi.hoisted). Feedback on these changes highlights a regression where TypeScript type checking was inadvertently disabled in both local and CI test scripts. Additionally, in chat-session.test.ts and template-chat-session.test.ts, the custom stubbing mechanism lacks a corresponding custom restore function to reset the mocked properties, which could lead to test pollution and flaky tests.
| "test": "run-p --npm-path npm lint test:all", | ||
| "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", |
There was a problem hiding this comment.
Removing type-check from the test script and changing test:ci to run test:all instead of test completely disables TypeScript type checking in both local testing and CI. This is a regression that could allow type errors to be merged. Please restore type-check to the test script and configure test:ci to run test.
| "test": "run-p --npm-path npm lint test:all", | |
| "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", | |
| "test": "run-p --npm-path npm lint type-check test:all", | |
| "test:ci": "node ../../scripts/run_tests_in_ci.js -s test", |
a5ab877 to
52d7592
Compare
- Replace anonymous stubs and manual afterEach null-resets with delegated mock implementations in count-tokens and generate-content tests. - Remove custom restore override in generative-model tests in favor of standard sinon.restore. - Use native Vitest runIf conditional runners in chrome-adapter and live-session-helpers tests to eliminate ESLint bypass comments. TAG=agy CONV=0e4d0bde-5c64-450d-ab5a-2a4f7489262c
| controller.abort(abortReason); | ||
|
|
||
| await expect(requestPromise).to.be.rejectedWith( | ||
| const assertion = expect(requestPromise).to.be.rejectedWith( |
There was a problem hiding this comment.
Attaching the rejection listener before ticking the clock. Otherwise, ticking the clock triggers the timeout and rejects the promise before the handler is attached, causing an UnhandledPromiseRejection error in Vitest.
| mockWebSocket.triggerMessage(new Blob([JSON.stringify({ foo: 2 })])); | ||
|
|
||
| await clock.tickAsync(5); | ||
| while (received.length < 2) { |
There was a problem hiding this comment.
[test improvement- not related to migration] Blob.text() is an async promise not controlled by fake timers. Wait for both messages to finish decoding before closing the socket.
| let webSocketStub: SinonStub; | ||
|
|
||
| beforeEach(() => { | ||
| if (typeof (globalThis as any).WebSocket === 'undefined') { |
There was a problem hiding this comment.
[test improvement- node tests crashing on node 20] Node 20 lacks global WebSocket (added in Node 22). This dummy class prevents sinon.stub(globalThis, 'WebSocket') from crashing on Node 20.
There was a problem hiding this comment.
Our CI runs in Node 22 and the next breaking release will require Node 24 so I think it's safe to not accommodate Node 20 in the tests. This way you also won't have to remember to remove globalThis.WebSocket in the afterEach, which we would have to add here if we kept this.
There was a problem hiding this comment.
Good catch! I've removed the fallback.
| import sinonChai from 'sinon-chai'; | ||
| import chaiAsPromised from 'chai-as-promised'; | ||
| import * as generateContentMethods from './generate-content'; | ||
|
|
There was a problem hiding this comment.
ESM modules are frozen in Vitest, so sinon.stub() can't modify them directly. This proxy delegates to real code by default, letting us keep existing Sinon stubs.
…n-Chai patterns - Replace legacy Chai assertions with native Vitest matchers - Eliminate Sinon stub and spy helpers in favor of native vi.spyOn and vi.fn implementations - Replace custom match and match.any helpers with expect.anything(), expect.objectContaining(), and expect.toSatisfy() - Establish test/setup.ts to restore mocks and reset timers across test runs
| import * as generateContentMethods from './generate-content'; | ||
| import { expect, vi, type Mock } from 'vitest'; | ||
|
|
||
| const { mockGenerateContent } = vi.hoisted(() => ({ |
There was a problem hiding this comment.
Had to read the docs a few times to really get it, as this is not super intuitive. I know we have to do this in many places so we don't want to put long comments every time, maybe just something short like "This inserts mockGenerateContent as a spy layer on methods coming from ./generate-content.ts"
There was a problem hiding this comment.
Added a brief comment explaining that this inserts mockGenerateContent as a spy layer on methods coming from ./generate-content.ts.
| @@ -437,11 +467,13 @@ describe('ChatSession', () => { | |||
| expect((e as unknown as any).name).to.equal('foo'); | |||
There was a problem hiding this comment.
I'm not totally sure but I think the reason we checked for the error.name instead of error.message was because sinon sets name instead of message. I think in vitest we can just check error.message here and we don't need to add the extra lines to add customErr.name.
There was a problem hiding this comment.
Updated! Replaced with a standard new Error('foo') in mockRejectedValue and checked (e as Error).message instead.
| let webSocketStub: SinonStub; | ||
|
|
||
| beforeEach(() => { | ||
| if (typeof (globalThis as any).WebSocket === 'undefined') { |
There was a problem hiding this comment.
Our CI runs in Node 22 and the next breaking release will require Node 24 so I think it's safe to not accommodate Node 20 in the tests. This way you also won't have to remember to remove globalThis.WebSocket in the afterEach, which we would have to add here if we kept this.
| expect(fetchStub).toHaveBeenCalledTimes(1); | ||
| }); | ||
|
|
||
| it('should throw DOMException if external signal is already aborted', async () => { |
There was a problem hiding this comment.
Change title as we don't check for DOMException anymore because toThrow only accepts one arg I guess?
There was a problem hiding this comment.
Updated the test titles to remove DOMException.
| export * from './schema'; | ||
| export * from './googleai'; | ||
| export { | ||
| export type { |
There was a problem hiding this comment.
This changes the production bundle slightly. I don't think it will hurt anything, but we need to make a changeset to mark the code change, get it released, have a commit to roll back to in case anyone has any errors, etc. Patch is fine.
f682267 to
d84ab4a
Compare
# Conflicts: # packages/ai/src/googleai-mappers.test.ts # packages/ai/src/methods/chat-session.test.ts # packages/ai/src/methods/chrome-adapter-browser.test.ts # packages/ai/src/methods/generate-content.test.ts # packages/ai/src/methods/live-session.test.ts # packages/ai/src/methods/template-chat-session.test.ts # packages/ai/src/requests/request-helpers.test.ts # packages/ai/src/requests/response-helpers.test.ts
Description
Migrates
@firebase/aiunit tests from legacy Karma & Mocha to Vitest (Node + Browser Chromium).Key Changes
vitest.config.mjsextendingconfig/vitest.base.mjsand removed deprecatedkarma.conf.js.npm testscripts to purevitestcommands (test:all,test:node,test:browser).globalreferences with standard ECMAScriptglobalThisinlive-session-helpers.test.ts.this.skip()anddescribeearly returns with conditional runner skips (describe.skip/it.skip) inchrome-adapter.test.ts.clock.tickAsyncinrequest.test.ts.websocket.test.ts.vi.mock('...', { spy: true })withvi.spyOn(...)andvi.resetAllMocks()intest/setup.tsfor native ESM browser execution across generative model and chat session tests.src/api.tsandsrc/types/index.ts.test/types/vitest-globals.d.tsfor isolated test typings.Testing & Impact