Jest (testing)
Created: 2018-03-05 11:02:55 -0800 Modified: 2020-02-05 09:08:19 -0800
Installation / basics
Section titled Installation / basicsInstallation:
- Install dev dependency packages. For me, these were
- babel-jest
- eslint
- eslint-plugin-jest
- jest
- If you didn’t already have eslint:
- eslint-plugin-import
- If you don’t already have it, Babel itself:
- babel-plugin-transform-object-rest-spread
- babel-preset-env
- I didn’t have a babelrc, so I needed to make one. Also, because I export already-transpiled ES6 classes in @botland/shared and ran into issues originally with trying to extend those classes, so I needed to add “exclude”: [“transform-es2015-classes”] as shown below:
-
Note that I am not on Babel 7, so apparently I can’t use browserslistrc and thus need to manually list out the browsers that I’m targeting.
-
Because I didn’t have a babelrc already but I did have a webpack.config.js, I needed to remove my babel configuration from webpack.
-
I needed a jest.config.js for ignoring LESS files (see below)
-
If you’re migrating from another framework like Mocha, Chai, etc., you can follow this guide
-
Configure .eslintrc.json
- Add “jest” to “plugins”
- Add “plugin:jest/recommended” to “extends”
-
Modified my client/package.json to add some scripts (reference)
- “test”: “jest”
- “test:coverage”: “jest —coverage && start ./coverage/lcov-report/index.html”,
- “test:watch”: “jest —watch”,
- “test:debug”: “node —inspect-brk ./node_modules/.bin/jest —runInBand —watch”,
- “test:debug:win”: “node —inspect-brk ./node_modules/jest/bin/jest.js —runInBand —watch”
- Note: the test:debug:win version actually works on all platforms.
-
Consider installing jest-watch-typeahead (reference)
- npm install —save-dev jest-watch-typeahead
- If you’re going to install this, you also need to modify your jest.config.js (reference)
- This also involves being on at least Jest v23, so you may need to do “npm install —save-dev jest@latest”.
- Once done, you can run “jest —watch”, then press enter to cut off any future tests, and finally follow the filter help that shows (generally just press “P” and start typing a file name).
Helpful setup guide (reference)
Section titled Helpful setup guide (reference)HiDeoo wrote up a nice starting guide for me (in the reference link above).
Testing using Jest in general (reference)
Section titled Testing using Jest in general (reference)Typically, you want to write your unit tests before you move on to anything scenario- or integration-related. When you DO get to that point, if you have thunks dispatching other thunks, you can use FlushThunks from redux-testkit so that you can wait for everything to finish before checking the state.
jest.config.js and “extending” a parent config
Section titled jest.config.js and “extending” a parent configThe file is just a JavaScript file, so you can do something like this:
THIS IS NOT A FUNCTION. It is just “.rejects.toThrow()” or “toThrowErrorMatchingSnapshot”:
So if you have a function that you would have called with “await someFunction”, you do what you see above:
toThrow
Section titled toThrowFor synchronous code, the “expect” call takes in a function, otherwise it wouldn’t be able to work because it would throw immediately:
Verifying an error was thrown with a custom property
Section titled Verifying an error was thrown with a custom propertyI sometimes have cases where I throw an Error from my development code that has a special property like “serverCode” or “clientCode”. From my test, I want to do something like this:
First, to explain the reasoning behind that code - we expect the function in the ‘try’ block to fail, so if it succeeds and we don’t have the “throw new Error”, then the test will pass unexpectedly.
Instead of “throw new Error”, the only real improvement that I could use here until Jest changes “toThrow” is to write this line:
expect(true).toBe(false);
Async “expects” (reference)
Section titled Async “expects” (reference)Suppose you have a function that you know should fail and you care about its error message:
You can do this with toThrowErrorMatchingSnapshot as well (I prefer this way):
You should not need expect.assertions unless you’re using callbacks, but if you’re using callbacks, then you have more to fix than just adding “expect.assertions”.
Jest extensions (reference)
Section titled Jest extensions (reference)If you ever want extra matchers like “toBeArray”, you can look at this package.
Mocks/mocking
Section titled Mocks/mockingSee this note.
Debugging (reference)
Section titled Debugging (reference)Because I’m on Windows and I have an old version of Node, here’s the command I have to run:
node —inspect-brk ./node_modules/jest/bin/jest.js —runInBand
If this doesn’t work for you, then it could require an update to at least Node v8.4 (see issue #1652)
TIMEOUT ISSUE: keep in mind that while debugging, the Jest default timeout per test of 5000 ms (reference) could be hit, and it may not be obvious that it’s happening in that case. To adjust the timeout of a test, the signature is “test(name, fn, timeout)”, so just put 1e9 as the timeout.
**Make sure not to check in the changed timeout! **
WATCH ISSUE (possibly fixed, check this): apparently you can’t use “—watch” when you’re debugging, so either specify the exact file that you want so that you don’t have to stop execution and filter down to it, or allow all files to run. To specify a single file, just do something like this:
node —inspect-brk ./node_modules/jest/bin/jest.js —runInBand ./test/react/components/hardwareloadout.test.js
DO NOT USE BACKSLASHES FOR THE TEST PATH UNLESS YOU DOUBLE THEM AS SHOWN BELOW:
.\test\react\components\hardwareloadout.test.js
I highly suggest reading on to “Sourcemaps” to find out how to get sourcemaps working, otherwise debugging could be very tough.
Sourcemaps
Section titled SourcemapsTurns out you don’t need to specify sourceMaps yourself in your babelrc because babel-jest will inline them for you (but if you did have to, here’s what it would look like):
{
“env”: {
“test”: {
“presets”: [
[“env”, {
“exclude”: [“transform-es2015-classes”],
}],
],
“sourceMaps”: “inline”
}
}
}
However, this can lead to strange issues debugging. For example, I had ES6 (which I wrote) that looked like this:
initialize() {
return this.setupDatabase().then(() => {
return this.databaseWrapper.connect();
});
}
However, the ES5 that it transpiled really turned “this” into “_this4”, so it was nearly impossible for me to actually debug using the console (due to this Chromium issue). Ways around this behavior:
- Turn off sourcemaps in Chrome (and refresh the page) so that you’re looking at transpiled code (reference)
- Note: when code has a sourcemap, you’ll see [sm] in the tab:
- For this specific problem, arrow functions shouldn’t even be transpiled since they’re supported natively by Node, so this was a configuration problem. I needed to make sure babel-preset-env knew to target the current version of Node:
“env”: {
“test”: {
“presets”: [
[“env”, {
“exclude”: [“transform-es2015-classes”],
targets: {
node: ‘current’,
},
}],
],
“sourceMaps”: false,
}
}
From Lumie1337: @Adam13531 regarding the sourcemaps issue, apparently it is an open issue for chrome, tldr: mapping from source code without source maps to source mapped symbols works, but not the other way around https://bugs.chromium.org/p/chromium/issues/detail?id=327092
Running common code between different tests
Section titled Running common code between different testsSimple example: suppose you always want to import SomeModule from every test. You can make use of setupTestFrameworkScriptFile:
- In jest.config.json, add
setupTestFrameworkScriptFile: ‘<rootDir>/test/setupjest.js’,
- Make a setupjest.js that just has “import SomeModule from whatever”;
Note that setupTestFrameworkScriptFile is very similar to setupFiles, so if you’re ever using them both, you may want to be careful about naming.
Custom “expect” logic (reference)
Section titled Custom “expect” logic (reference)I was testing Joi and noticed that it was emitting “then” and “catch” properties everywhere even though they weren’t relevant to my tests. Originally, I just called “delete” on both of those, but then HiDeoo showed me the reference link, and now I can do something like this:
14:28 HiDeoo: Ho nvm Adam13531, I remembered it wrong, it’s not yet doable as-is in an expect.extend, we use a fork to have toMatchSnapshotAndIgnoreKeys() but they don’t expose the original toMatchSnapshot() in expect.extend so we had to fork it to expose it. There is a PR coming up for this to expose it but not yet merged.
Note: if you’re going to go this route, the location where you’d add the code would be based on setupTestFrameworkScriptFile in your jest.config.js (reference). I wrote a config like this:
module.exports = {
setupTestFrameworkScriptFile: ‘<rootDir>/test/setuptestframework.js’,
};
Coverage
Section titled CoverageAll I had to do is put this in my package.json file under “scripts”:
“test:coverage”: “jest —coverage”
Then I did “npm run test:coverage” and got a coverage/lcov-report/index.html that I could open.
Collecting coverage from other files (reference)
Section titled Collecting coverage from other files (reference)All you have to do is make a jest.config.js that looks like this:
module.exports = {
collectCoverageFrom: [‘<rootDir>/whatever/**/*.js’],
};
Note: “<rootDir>” is actually a token for Jest indicating the root directory.
You technically can cover files in node_modules even though Istanbul ignores them by default (and Jest uses a fork of Istanbul). Check out this issue for help with setting it up. Here’s a public example of that in action.
Using snapshots
Section titled Using snapshotsWhen running a test with “toMatchSnapshot” for the first time, a snapshots directory is going to get created alongside the test. This will contain some JavaScript objects/strings that represent what was returned by your test.
Subsequently, it will compare the output of the test against whatever’s in the snapshot files. Suppose you run a test and it reports a mismatch against the snapshot (meaning the test has errors), but you don’t want them to be considered as errors, you can press ‘u’ in “jest —watch” to update the snapshot files.
You should check in your snapshots folders.
If you expect your test to fail, then instead of saying something like:
say
Inline snapshots
Section titled Inline snapshotsTo get snapshots directly into your test code without needing a new file, take a look at this:
[13:53] HiDeoo: Btw adam13531 I don’t know what Jest version you’re using, but in 23.3 they introduced inline snapshots with toMatchInlineSnapshot() & toThrowErrorMatchingInlineSnapshot() and it’s amazing, no more snapshot files & you can see the snapshot right from the test https://bit.ly/2mFmqs6
Dynamic data in snapshots (“property matchers”) (reference)
Section titled Dynamic data in snapshots (“property matchers”) (reference)If you have a generated user ID or date, you may only want to check that the type is correct. This is where property matchers come into play. However, there may be a time when you want to ignore a property altogether (e.g. a database property that could be null or a date); in those cases, you’re not really testing anything (since the database will enforce that constraint for you just by virtue of columns having types), so just omit the property from the test:
Here’s another example:
Handling LESS files (reference)
Section titled Handling LESS files (reference)Just follow the instructions at the reference; it’s an installation and a modification to jest.config.json. The way that it works is it will return the name used to index your CSS modules. E.g. if, in production, you specify “styles.button” and you end up getting “_1VRY1S3Kahz8E2HlNBR-U5”, then in your test, it would literally show as the string “button” (since that’s the key that you used).
There are a couple of reasons why you might want to handle LESS files instead of just ignoring them:
- Your snapshots of DOM elements will make more sense rather than having “class=undefined” (or class="") everywhere
- You may want to fetch an element by its class, in which case having the identifier without having to modify dev code is desirable.
Ignoring LESS files (reference)
Section titled Ignoring LESS files (reference)Just FYI: you can “properly” handle LESS files too (reference). If you go that route, then you won’t get “class=undefined” or ‘class=""’ for all of your DOM snapshots.
The reference clearly describes two ways. If you want to go the config route, put it into jest.config.js:
Note that <rootDir> is not just for the sake of example. Also, you’ll need to restart Jest after doing this.
Troubleshooting
Section titled TroubleshootingBabelrc issues
Section titled Babelrc issuesI am pretty sure I ran into this issue. It was frustrating enough where I just explicitly put my babelrc back into Webpack for the sake of deploys and kept a separate .babelrc file just for sake of Jest.
If I ever need to take another look at this, I could specify “debug: true” in babelrc so that I can figure out which plug-ins are being used.
console.time
Section titled console.timeThere’s a bug in console.time at the time of writing (6/12/2018) at these two lines of code: they divide by 1000 but then show the resulting time in ms. This means that if you use console.time and your times seem off by a factor 1000 that it’s not you. ;)
UPDATE (6/20/2018): this has been fixed
Random failure that you can’t chalk up to anything else
Section titled Random failure that you can’t chalk up to anything elseIt’s possible that you forgot to reset mocks. If running the test in isolation works, then this is a stronger possibility since it means you didn’t clean up after a previous test.
Comparing dates
Section titled Comparing datesI was trying to compare dates that I’d received from knex, but it wasn’t working. This code:
…was producing this result:
Expected: 2018-07-31T21:58:59.000Z
Received: 2018-07-31T21:58:59.000Z
I should have been using “toEqual” instead of “toBe” and it would have worked (reference).