My “Wow, I didn't know that!” Moments with Jest
- Transfer
Hello! The JavaScript Developer course kicks off this Thursday. In this regard, we decided to share the translation of another interesting material. Enjoy reading.
Jest has always been my indispensable unit testing tool. It is so reliable that I start to think that I have always underused it. Although the tests worked, over time I refactored them here and there, because I did not know that Jest could do that. Each time this is a new code when I check the Jest documentation.
So, I'm going to share some of my favorite Jest tricks that some of you may already know because you read the documentation and not like me (shame), but I hope this helps those who just ran through it quickly !
By the way, I use Jest v24.8.0 as reference material, so be careful, some things may not work on the version of Jest that you are currently using. In addition, the examples do not represent the actual test code, this is just a demonstration.
At first, all these statements looked normal to me:
Based on the use of chai for equality statements (to.equal), this is just natural. In fact, Jest will not complain, and these statements pass as usual.
However, Jest has .toBe and .toEqual. The first is used to assert equality using Object.is , and the second is to provide a deep comparison of objects and arrays. .toEqual has a fallback to using Object.is if it turns out that you do not need a deep comparison, such as asserting equalities for primitive values, which explains why the previous example went very well.
This way you can skip everything
A common mistake is what you will use
If you look at the source code when .toBe crashes, it will try to determine if you really made this error by calling the function that .toEqual uses. This can be a bottleneck when optimizing your test.
If you are sure that you are using primitive values, your code can be reorganized as such for optimization purposes:
More details in the documentation .
Technically, you can use
These are just some of the ones that I selected from the long list of Jest comparisons in the documentation, you can take a look at the rest yourself.
You may have heard about snapshot testing in Jest , where it helps track changes in your user interface elements. But testing with snapshots is not limited to this.
Consider this example:
It would be tiring if you claimed more and more employees. Also, if it turns out that more claims are needed for each employee, multiply the number of new claims by the number of employees, and you get an idea.
With snapshot testing, all this can be done simply like this:
Whenever regressions occur, you would know exactly which tree in the node does not match the image.
But convenience comes at a price: this method is more error prone. There is a possibility that you will not know that the picture is actually incorrect, and in the end you will take it anyway. So double check your snapshot as if it is your own approval code (because it is).
Of course, testing is not limited to snapshots. Read full information in the documentation .
Have you ever written a test that is somewhat similar to this?
This is monotonous and routine, right? Imagine doing this with a lot of cases.
Using
However, I have yet to use this in my own test, as I prefer my test to be detailed, but I just thought it would be an interesting trick.
See the documentation for more details on the arguments (spoiler: the table syntax is really cool).
At some point, you will have to test something that depends on global functions in a particular test case. For example, a function that receives information about the current date using the Date javascript object, or a library that relies on it. The difficulty is that when it comes to the current date, you can never get the right statement.
In the end, you would have to redefine the global Date object so that it is consistent and manageable:
However, this is considered bad practice, as redefinition is maintained between tests. You will not notice this if there is no other test based on Date.now, but it will leak too.
I used to “crack” it so that it would not leak:
However, there is a much better, less hacker way to do this:
Thus,
There is definitely more information on stubs in Jest. See full documentation for more details.
This article is getting long enough, so I think that's all for now. This affects only a small part of the capabilities of Jest, and I just highlight my favorites. If you have other interesting facts, let me know.
And also, if you used Jest often, check out Majestic, which is a GUI-free GUI for Jest, is a really good alternative to boring terminal output. I'm not sure if there is an author in dev.to, but nonetheless respect for this person.
As always, thank you for your attention!
That's all. See you on the course.
Jest has always been my indispensable unit testing tool. It is so reliable that I start to think that I have always underused it. Although the tests worked, over time I refactored them here and there, because I did not know that Jest could do that. Each time this is a new code when I check the Jest documentation.
So, I'm going to share some of my favorite Jest tricks that some of you may already know because you read the documentation and not like me (shame), but I hope this helps those who just ran through it quickly !
By the way, I use Jest v24.8.0 as reference material, so be careful, some things may not work on the version of Jest that you are currently using. In addition, the examples do not represent the actual test code, this is just a demonstration.
#one. .toBe vs .toEqual
At first, all these statements looked normal to me:
expect('foo').toEqual('foo')
expect(1).toEqual(1)
expect(['foo']).toEqual(['foo'])
Based on the use of chai for equality statements (to.equal), this is just natural. In fact, Jest will not complain, and these statements pass as usual.
However, Jest has .toBe and .toEqual. The first is used to assert equality using Object.is , and the second is to provide a deep comparison of objects and arrays. .toEqual has a fallback to using Object.is if it turns out that you do not need a deep comparison, such as asserting equalities for primitive values, which explains why the previous example went very well.
expect('foo').toBe('foo')
expect(1).toBe(1)
expect(['foo']).toEqual(['foo'])
This way you can skip everything
if-else
in .toEqual
, using .toBe
if you already know what values you are testing. A common mistake is what you will use
.toBe
to assert equality of primitive values.expect(['foo']).toBe(['foo'])
If you look at the source code when .toBe crashes, it will try to determine if you really made this error by calling the function that .toEqual uses. This can be a bottleneck when optimizing your test.
If you are sure that you are using primitive values, your code can be reorganized as such for optimization purposes:
expect(Object.is('foo', 'foo')).toBe(true)
More details in the documentation .
# 2 More suitable comparisons
Technically, you can use
.toBe
to validate any values. With Jest, you can specifically use certain comparison tools that will make your test more readable (and in some cases even shorter).//
expect([1,2,3].length).toBe(3)
//
expect([1,2,3]).toHaveLength(3)
const canBeUndefined = foo()
//
expect(typeof canBeUndefined !== 'undefined').toBe(true)
//
expect(typeof canBeUndefined).not.toBe('undefined')
//
expect(canBeUndefined).not.toBe(undefined)
//
expect(canBeUndefined).toBeDefined()
class Foo {
constructor(param) {
this.param = param
}
//
expect(new Foo('bar') instanceof Foo).toBe(true)
//
expect(new Foo('bar')).toBeInstanceOf(Foo)
These are just some of the ones that I selected from the long list of Jest comparisons in the documentation, you can take a look at the rest yourself.
# 3 Snapshot testing on elements without a user interface
You may have heard about snapshot testing in Jest , where it helps track changes in your user interface elements. But testing with snapshots is not limited to this.
Consider this example:
const allEmployees = getEmployees()
const happyEmployees = giveIncrementByPosition(allEmployees)
expect(happyEmployees[0].nextMonthPaycheck).toBe(1000)
expect(happyEmployees[1].nextMonthPaycheck).toBe(5000)
expect(happyEmployees[2].nextMonthPaycheck).toBe(4000)
// ...etc
It would be tiring if you claimed more and more employees. Also, if it turns out that more claims are needed for each employee, multiply the number of new claims by the number of employees, and you get an idea.
With snapshot testing, all this can be done simply like this:
const allEmployees = getEmployees()
const happyEmployees = giveIncrementByPosition(allEmployees)
expect(happyEmployees).toMatchSnapshot()
Whenever regressions occur, you would know exactly which tree in the node does not match the image.
But convenience comes at a price: this method is more error prone. There is a possibility that you will not know that the picture is actually incorrect, and in the end you will take it anyway. So double check your snapshot as if it is your own approval code (because it is).
Of course, testing is not limited to snapshots. Read full information in the documentation .
#4. describe.each and test.each
Have you ever written a test that is somewhat similar to this?
describe('When I am a supervisor', () => {
test('I should have a supervisor badge', () => {
const employee = new Employee({ level: 'supervisor' })
expect(employee.badges).toContain('badge-supervisor')
})
test('I should have a supervisor level', () => {
const employee = new Employee({ level: 'supervisor' })
expect(employee.level).toBe('supervisor')
})
})
describe('When I am a manager', () => {
test('I should have a manager badge', () => {
const employee = new Employee({ level: 'manager' })
expect(employee.badges).toContain('badge-manager')
})
test('I should have a manager level', () => {
const employee = new Employee({ level: 'manager' })
expect(employee.level).toBe('manager')
})
})
This is monotonous and routine, right? Imagine doing this with a lot of cases.
Using
description.each
and test.each
you can compress the code as follows:const levels = [['manager'], ['supervisor']]
const privileges = [['badges', 'toContain', 'badge-'], ['level', 'toBe', '']]
describe.each(levels)('When I am a %s', (level) => {
test.each(privileges)(`I should have a ${level} %s`, (kind, assert, prefix) => {
const employee = new Employee({ level })
expect(employee[kind])[assert](`${prefix}${level}`)
})
})
However, I have yet to use this in my own test, as I prefer my test to be detailed, but I just thought it would be an interesting trick.
See the documentation for more details on the arguments (spoiler: the table syntax is really cool).
#5. Single imitation of global functions
At some point, you will have to test something that depends on global functions in a particular test case. For example, a function that receives information about the current date using the Date javascript object, or a library that relies on it. The difficulty is that when it comes to the current date, you can never get the right statement.
function foo () {
return Date.now()
}
expect(foo()).toBe(Date.now())
// This would throw occasionally:
// expect(received).toBe(expected) // Object.is equality
//
// Expected: 1558881400838
// Received: 1558881400837
In the end, you would have to redefine the global Date object so that it is consistent and manageable:
function foo () {
return Date.now()
}
Date.now = () => 1234567890123
expect(foo()).toBe(1234567890123) //
However, this is considered bad practice, as redefinition is maintained between tests. You will not notice this if there is no other test based on Date.now, but it will leak too.
test('First test', () => {
function foo () {
return Date.now()
Date.now = () => 1234567890123
expect(foo()).toBe(1234567890123) //
})
test('Second test', () => {
function foo () {
return Date.now()
expect(foo()).not.toBe(1234567890123) // ???
})
I used to “crack” it so that it would not leak:
test('First test', () => {
function foo () {
return Date.now()
const oriDateNow = Date.now
Date.now = () => 1234567890123
expect(foo()).toBe(1234567890123) //
Date.now = oriDateNow
})
test('Second test', () => {
function foo () {
return Date.now()
expect(foo()).not.toBe(1234567890123) // as expected
})
However, there is a much better, less hacker way to do this:
test('First test', () => {
function foo () {
return Date.now()
jest.spyOn(Date, 'now').mockImplementationOnce(() => 1234567890123)
expect(foo()).toBe(1234567890123) //
})
test('Second test', () => {
function foo () {
return Date.now()
expect(foo()).not.toBe(1234567890123) // as expected
})
Thus,
jest.spyOn
following the global Date object imitates the implementation of the now function for only one call. This, in turn, will leave Date.now intact for the rest of the tests. There is definitely more information on stubs in Jest. See full documentation for more details.
This article is getting long enough, so I think that's all for now. This affects only a small part of the capabilities of Jest, and I just highlight my favorites. If you have other interesting facts, let me know.
And also, if you used Jest often, check out Majestic, which is a GUI-free GUI for Jest, is a really good alternative to boring terminal output. I'm not sure if there is an author in dev.to, but nonetheless respect for this person.
As always, thank you for your attention!
That's all. See you on the course.