Some background to understand and write tests for the nns-dapp.
Tests are in ./frontend/src/tests
.
$ cd frontend
$ npm test
Tests in frontend/src/tests/lib
and frontend/src/tests/routes
are the unit tests for each component or functions we have.
A simple component test example can be AccountBadge.spec.ts
.
On the other hand, check accounts.utils.spec
for function testing.
Test in src/tests/workfows
are considered more of integration tests. The main goal is to test whole workflows mocking only the API layer.
For example in CreateSubaccount.spec.ts
it renders the Accounts page and performs the actions to create a subaccount.
There are a few rules to consider a test as an integration test:
- Mock only the API layer and
authStore
. - Set the stores to any value needed to check the flow you're testing.
- No mocking of services, utilities, stores, ...
- Do not use CSS classes as selector.
- Expect calls to the API layer.
All the mocks used in different tests are in frontend/src/tests/mocks
.
Found in frontend/src/tests/utils
.
To migrate a test from jest to vitest, aside from moving it between folders, the following changes can be made:
-
Replace the prefix
jest.
withvi.
-
Remove the
jsdom
annotation, as all vitest runs in a simulated browser environment
/**
* @jest-environment jsdom
*/
-
Replace
jest-mock-extended
withvitest-mock-extended
-
Replace path
$tests
with$vitests
in following imports:$tests/utils/timers.test-utils
$tests/utils/utils.test-utils
-
In vitest,
requireActual
is replaced byimportActual
, and it also becomes a promisified function
// jest
jest.mock("$lib/services/ckbtc-minter.services", () => {
return {
...jest.requireActual("$lib/services/ckbtc-minter.services"),
loadBtcAddress: jest.fn().mockImplementation(() => undefined),
};
});
// vitest
vi.mock("$lib/services/ckbtc-minter.services", async () => {
return {
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
...(await vi.importActual<any>("$lib/services/ckbtc-minter.services")),
loadBtcAddress: vi.fn().mockImplementation(() => undefined),
};
});
- Replace
jest.Mock
cast with typesMock
// jest
(registerHardwareWalletProxy as jest.Mock).mockImplementation(async () => {
// Do nothing test
});
// vitest
import type { Mock } from "vitest";
(registerHardwareWalletProxy as Mock).mockImplementation(async () => {
// Do nothing test
});
- Replace
jest.SpyInstance
with typesSpyInstance
// jest
let spyQueryAccount: jest.SpyInstance;
// vitest
import type { SpyInstance } from "vitest";
let spyQueryAccount: SpyInstance;
- Module mocks require
default
exports with vitest. When not migrated, a common error thrown by the test is the following:
> Error: [vitest] vi.mock("$lib/workers/balances.worker?worker", factory?: () => unknown) is not returning an object. Did you mean to return an object with a "default" key?
// jest
jest.mock("$lib/workers/transactions.worker?worker", () => {
return class TransactionsWorker {
// Etc.
};
});
// vitest
vi.mock("$lib/workers/transactions.worker?worker", () => ({
default: class TransactionsWorker {
// Etc.
},
}));
-
When
ReferenceError
is thrown, it can be linked to mocks that are declared within the test and not at its top. This can often be solved by transforming thejest.mock
into avi.doMock
(instead ofvi.mock
). -
The pattern
await waitFor(expect().toBeNull)
seems to require an explicit arrow function call for vitest, like this:await waitFor(() => expect().toBeNull())
. -
done() callback is deprecated and should be replaced by promise. It's worth noting that this isn't an issue per se, but it does trigger a console.log, which is not allowed by our test suite.
// jest
it("should work", (done) => {
done();
});
// vijest
it("should work", () =>
new Promise<void>((done) => {
done();
}));
For additional information, if needed, refer to the official guide on Migrating from Jest.
Tests are found in rs/backend/src/accounts_store/tests.rs
.
$ cargo test --lib