I want to RSpec even at Jest!

Introduction

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! !!

Try to find a library

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? _

The wall that stands up

Implementing a module that extends Jest can't be that easy: innocent:

Lazy evaluation variable

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

Lazy evaluation matcher

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].

Method chain

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)

How to use

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.

Finally

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

I want to RSpec even at Jest!
I want to test Action Cable with RSpec test
I want to be eventually even in kotlin
I want to convert characters ...
I want to click a GoogleMap pin in RSpec
Even in Java, I want to output true with a == 1 && a == 2 && a == 3
I want to easily back up files used at work
I want to create a Parquet file even in Ruby
Swift: I want to chain arrays
I want to use FormObject well
I want to convert InputStream to String
I want to docker-compose up Next.js!
I want to develop a web application!
I want to write a nice build.gradle
I want to make an ios.android app
I want to display background-ground-image on heroku.
I want to use DBViewer with Eclipse 2018-12! !!
I want to write a unit test!
I want to install PHP 7.2 on Ubuntu 20.04.
I want to stop Java updates altogether
I want to use @Autowired in Servlet
I want to target static fields to @Autowired
I want to do team development remotely
Even in Java, I want to output true with a == 1 && a == 2 && a == 3 (PowerMockito edition)
I want to sort by tab delimited by ruby
I want to output the day of the week
Run R from Java I want to run rJava
[Swift] I want to draw grid lines (squares)
I want to send an email in Java.
I want to graduate from npm install properly [2020]
I want to use arrow notation in Ruby
Even in Java, I want to output true with a == 1 && a == 2 && a == 3 (Javassist second decoction)
[Ruby] I want to do a method jump!
I want to use java8 forEach with index
I want to var_dump the contents of the intent
I want to pass APP_HOME to logback in Gradle
I want to simply write a repeating string
I want to design a structured exception handling
rsync4j --I want to touch rsync in Java.
I want to play with Firestore from Rails
[Xcode] I want to manage images in folders
I want to write quickly from java to sqlite
I want to truncate after the decimal point
I want to reduce simple mistakes. To command yourself.
I want to perform aggregation processing with spring-batch
Even in Java, I want to output true with a == 1 && a == 2 && a == 3 (black magic edition)
[Rails] I want to load CSS with webpacker
I want to delete files managed by Git
I want to get the value in Ruby
[Rails] I want to test with RSpec. We support your step [Introduction procedure]
Rspec: I want to test the post-execution state when I set a method on subject
I want to place RadioButtons in the same RadioGroup at any position on the screen.
I want to use Combine in UIKit as well.
I want to use Clojure's convenient functions in Kotlin
I want to call a method of another class
I want to do something like "cls" in Java
I want to embed any TraceId in the log
Pointcut Expression I want to specify more than one
I want to use fish shell in Laradock too! !!
I want to use ES2015 in Java too! → (´ ・ ω ・ `)
I learned stream (I want to convert List to Map <Integer, List>)