How to migrate from jasmine to jest
This is a guide to replace the jest-jasmine2 runner with the jest-default runner. Additionally, you will find a cheat sheet to write tests without using jasmine.
Why?
Jest has been flipping defaults with version 27. It is explained in the Jest blog from May 25, 2021:
»Up until now, if you were using Jest in its default configuration, you were - perhaps unknowingly - running some code forked many years ago from the test runner Jasmine 2.0 ...
... In the next major, we plan to also eliminate "jest-jasmine2" and "jest-environment-jsdom" from the Jest dependency tree and require them to be installed explicitly, so that many users can profit from a smaller install size with less clutter that they don't need.«
Jest blog
How can you identify if you are using jest-jasmine2?
If you are running »some code forked many years ago« you can find out by
- running
npx jest --showConfig
- looking in your
jest.config.js
files
Either way will tell you about the testRunner you are using.
testRunner: 'jest-jasmine2',
How to proceed?
Step one: replace everything
At leanix we are using a nx monorepo. This enables us to remove the jest-jasmine2
test runners per nx library. Removing it from one jest.config.js
file will only affect existing tests that rely on this configuration.
The easiest way to remove the jasmine references from your test is by replacing commonly used jasmine patterns.
Search and replacements examples
The following expression are examples. You might need to adapt them for your own project.
Search | Replace | |
---|---|---|
spyOn() | spyOn((.+?, '\w+?')); | jest.spyOn($1); |
.callThrough() | spyOn((.+?, '\w+?')).and.callThrough(); | jest.spyOn($1); |
spyOn | spyOn | jest.spyOn( |
jasmine.createSpy(); | jasmine.createSpy(); | jest.fn() |
.calls.mostRecent(); | .calls.mostRecent(); | .mock.calls[0]; |
jasmine fail | fail, | (error) => {throw new Error(error);}, |
jasmine.arrayContaining() | jasmine.arrayContaining( | expect.arrayContaining( |
jasmine.objectContaining() | jasmine.objectContaining( | expect.objectContaining( |
.throwError() | spyOn((.+?, '\w+?')).and.throwError((.+?)); | jest.spyOn($1).mockImplementation(() => {throw new Error($2)}); |
.callFake() | spyOn((.+?, '\w+?')).and.callFake((.+?)); | jest.spyOn($1).mockImplementation($2); |
jasmine.Spy; | : jasmine.Spy; | : jest.SpyInstance; |
.returnValue() | spyOn((.+?, '\w+?')).and.returnValue( | jest.spyOn($1).mockReturnValue( |
.returnValues() | spyOn((\w+?, '\w+?')).and.returnValues((.+?)); | jest.spyOn($1).mockReturnValue($2); |
returnValues()
needs manual adjustments (or a niftier regex). I did not adapt this since it is not used very often in our codebase and manual replacement did not cause much effort.
This is an example how the replacement could look like in the end:
$2 => .mockReturnValue(a).mockReturnValue(b)
Step two: fix occurring issues
This tends to be the more time-consuming step. If you have replaced all the above jasmine references, just run your test for this library/app that you have been working on and fix the tests that are now failing.
Frequently occurring issues:
- There are still more jasmine references left (It depends for example on your code formatting and if your regular expressions span multiple lines. Also, not all possible references are mentioned above.) Just keep on replacing.
- The
jest.spyOn()
function by default calls through. That means if some of the underlying logic uses state and requires some values to be defined it will fail. An easy fix is to add.mockReturnValue()
to the spy that causes the issue. - A test fails when run in line with other tests, but does not fail, when run alone. This could mean that a spy is not restored during testing. It could be fixed by calling
jest.restoreAllMocks()
in anafterEach()
block.
afterEach(() => jest.restoreAllMocks());
- "TypeError: Converting circular structure to JSON" might hint to an issue with async testing. Try setting up async and await properly.
- "Test functions cannot both take a 'done' callback and return something." and "Expected done to be called once, but it was called multiple times." could mean that you could remove your
done
callback function entirely for this test. - ngMocks MockInstance uses jasmine. You would have to replace the
MockComponent()
in your test declarations with the actual component and provide everything that is needed to fix the tests. Depending on how oftenMockInstance
is used in your code this could turn out to be quite some work.
Outlook
Everything is working again, and you want to provide some help for your team to write tests without jasmine. Here is a cheat sheet for you:
Cheat sheet to write future tests without using jasmine:
If your tests are suddenly failing, try restoring your mocks after each test:
afterEach(() => {
jest.restoreAllMocks();
});
If you just want to restore one mock between tests use:
afterEach(() => {
spy.mockRestore();
})
jasmine.callThrough();
JASMINE
spyOn(someService, "someFunction").and.callThrough();
JEST
jest.spyOn(someService, "someFunction");
(callThrough behaviour is default in jest)
.createSpy();
JASMINE
jasmine.createSpy()
JEST
jest.fn()
.calls.mostRecent();
JASMINE
.calls.mostRecent();
JEST
.mock.calls[0];
jasmine.Spy;
JASMINE
jasmine.Spy;
JEST
jest.SpyInstance;
.and.returnValue();
JASMINE
spyOn(someService, "someFunction").and.returnValue("a")
JEST
jest.spyOn(someService, "someFunction").mockReturnValue("a")
.and.returnValues();
JASMINE
spyOn(someService, "someFunction").and.returnValues(["a", "b"]);
JEST
jest.spyOn(someService, "someFunction").mockReturnValueOnce("a").mockReturnValueOnce("b");
.mockReturnValueOnce for each value you want to return
spyOn();
JASMINE
spyOn(someService, "someFunction");
JEST
jest.spyOn(someService, "someFunction");
.throwError();
JASMINE
spyOn(someService, "someFunction").and.throwError("Error");
JEST
jest.spyOn(someService, "someFunction").mockImplementation(()=> {throw new Error("Error")});
fail
JASMINE
testObservable$.subscribe(()=> {}, fail, done)
JEST
testObservable$.subscribe(() => {}, (error)=> {throw new Error(error)}, done)
Or replace the subscribe entirely with something like await firstValueFrom(x)
instead.
jasmine.arrayContaining() & jasmine.objectContaining()
JASMINE
jasmine.arrayContaining();
jasmine.objectContaining();
JEST
expect.arrayContaining();
expect.objectContaining();
.callFake()
JASMINE
spyOn(someService, "someFunction").and.callFake(()=> {return "a"});
JEST
spyOn(someService, "someFunction").mockImplementation(()=> {return "a"});
jasmine.anything()
JASMINE
jasmine.anything()
JEST
expect.anything()
Published by Dahlia de Candido
Visit author page