Why ** Jest ** isn't ** RSpec **! !! !! !! !! !! !! !! !! !! !! : japanese_goblin: If you are an RSpec addict, you might think once.
If you are such a person, please consult your family doctor once.
It was too late for me, so I made it possible to RSpec in Jest as well. If there are other people who are too late, please refer to them! !!
First, let's find the library.
I couldn't find anything that implements around the mock. It can't be helped, so let's make it! ... _How hard can it be? _
Implementing a module that extends Jest can't be that easy: innocent:
Consider the implementation of a lazy evaluation variable (let).
Jest can create a test group (Suite) with describe
. To create a lazy evaluation variable (let), you need to be able to see the current test group at definition and invocation.
However, Jest has no way to reference them externally.
Since it was possible to refer to all the defined test groups in a tree structure, it was solved by recursively searching for the test group that owns the test while the test is being executed and the endmost Suite is in the current context during the test definition. Did.
https://github.com/alfa-jpn/jest-rspec-style/blob/master/src/jest_rspec_styles/jest_tracker.ts#L19
Consider an implementation of RSpec's expect (...). To receive (...)
matcher.
This matcher verifies that the mock is called after it is declared and before the end of the test.
As a general rule, Jest's custom matchers must return results on the fly.
You can delay the evaluation by returning a Promise, but if you do not wait for the processing to complete with await, only UnhandledPromiseRejectionWarning
will occur and the test result will not be reflected.
So it's a bit brute force, but I've implemented toReceive
, which is something other than Jest's matcher-like Jest matcher, in the form of` wrap and mix into jest's matcher [https://github.com/alfa-jpn/jest-rspec-style/blob/master/src/jest_rspec_styles/matcher.ts#L32].
Consider the implementation of the change
matcher.
RSpec allows you to define test content in a method chain.
expect { subject }.to change(hoge, :count).from(1).to(2)
Jest also considered implementing a similar mechanism, but it is not straightforward.
There is no way to determine the end of the chain (test definition complete), and it is difficult to control when the test is executed.
In the case of RSpec, it is solved by using expect (...). To
and later as the argument of the # to
method.
expect { subject }.to change(hoge, :count).from(1).to(2)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#Actually here is`#to`Arguments of
It can be considered that the test definition is completed when the chain is passed as an argument.
If you try to implement the same mechanism with Jest, you can't omit the parentheses of the function call, so you end up with this crappy implementation.
expect(subject).to(change(hoge, :count).from(1).to(2))
I was able to work with this kind of implementation with a little more ingenuity.
expect(subject)(to.change(hoge, :count).from(1).to(2))
I felt a little radical and I doubted the readability, so I decided not to introduce it.
There are some other points, but it's completed like this.
jest-rspec-style jest-rspec-style.
The context of RSpec and the wonders of lazy evaluation variables aren't covered in this article as there are many great articles out there. Please refer to that.
-Introduction to RSpec that can be used, Part 1 "Understanding the basic syntax and useful functions of RSpec" -Use of (describe/context/example/it) of RSpec properly
The following functions can be used in this library.
--lazy (function equivalent to let)
Create spec_helper.js like this.
import JestRSpecStyle from 'jest-rspec-style'
JestRSpecStyle.setup()
Please import in each test.
import './spec_helper'
describe('Hoge', () => {
// ...
})
Don't you think you already have RSpec?
lazy It is a function equivalent to let of RSpec. Renamed to lazy to interfere with ES6 let. Used in the test definition and references the last defined lazy evaluation variable when the test is run.
//
// jest
//
price = undefined
beforeEach(() => {
price = 100
})
it('The price of sushi is 100 yen', () => {
expect(price).toEqual(100)
})
//
// jest-rspec-style
//
lazy('price', () => 100)
it('The price of sushi is 100 yen', () => {
expect(lazy('price')).toEqual(100)
})
context
It is used when creating a conditional branch test group.
//
// jest
//
it('If the price of sushi is 100 yen, 10 yen tax will be collected.')
it('If the price of sushi is 100 yen, eat sushi')
it('If the price of sushi is 200 yen, a tax of 20 yen will be collected.')
it('If the price of sushi is 200 yen, don't eat sushi')
//
// jest-rspec-style
//
context('When the price of sushi is 100 yen', () => {
it('10 yen tax will be collected')
it('Eating sushi')
})
context('When the price of sushi is 200 yen', () => {
it('20 yen tax will be collected')
it('Don't eat sushi')
})
sharedExamples / includeExamples It is a function to insert a common test item as a context.
//
// jest
//
it('For 100 yen sushi, the consumption tax must be 10 yen', () => {
expect(Sushi.tax(100)).toEqual(10)
})
it('For 200 yen sushi, the consumption tax must be 20 yen', () => {
expect(Sushi.tax(200)).toEqual(20)
})
it('For sushi of 300 yen, the consumption tax must be 30 yen', () => {
expect(Sushi.tax(300)).toEqual(30)
})
//
// jest-rspec-style
//
sharedExamples('Verification of sushi consumption tax', (price, tax) => {
it(`${price}In the case of yen sushi, the consumption tax is${tax}Being a circle`, () => {
expect(Sushi.tax(price)).toEqual(tax)
})
})
includeExamples('Verification of sushi consumption tax', 100, 10)
includeExamples('Verification of sushi consumption tax', 200, 20)
includeExamples('Verification of sushi consumption tax', 300, 30)
toChange Verify the value change before and after the method execution.
//
// jest
//
expect(sushi).toEqual(1)
sushi++;
expect(sushi).toEqual(2)
//
// jest-rspec-style
//
expect(() => sushi++).toChange(() => sushi, { from: 1, to: 2 })
allow / allowAnyInstanceOf Mock the method of the specified object.
//
// jest
//
jest.spyOn(sushi, 'getName').mockReturnValue('Salmon')
jest.spyOn(Sushi.prototype, 'getName').mockReturnValue('Salmon')
var mock = new Sushi()
jest.spyOn(mock, 'getName').mockReturnValue('Salmon')
jest.spyOn(store, 'takeSushi').mockReturnValue(mock)
//
// jest-rspec-style
//
allow(sushi).toRceive('getName').andReturn('Salmon')
allowAnyInstanceOf(Sushi).toRceive('getName').andReturn('Salmon')
allow(store).toReceiveMessageChain('takeSushi', 'getName').andReturn('Salmon')
The following chain methods are available after toReceive.
toReceive / expectAnyInstanceOf Mock the specified object and verify that it is called.
//
// jest
//
var mock = jest.spyOn(human, 'eat')
human.eat('Tuna')
human.eat('Tuna')
expect(mock).toHaveBeenCalledTimes(2)
expect(mock).toHaveBeenCalledWith('Tuna')
//
// jest-rspec-style
//
expect(human).toReceive('eat').with('Tuna').times(2)
human.eat('Tuna')
human.eat('Tuna')
//You can target all instances by using expectAnyInstance instead of expect
expectAnyInstanceOf(Human).toReceive('eat)
After toReceive, the following methods are available in addition to the allow chain method.
So I was able to Rspec at Jest as well. We are aiming for a package that is friendly to RSpec addicts, so we are waiting for PR! !! !! : pray:
Recommended Posts