Skip to content
This repository has been archived by the owner on Mar 5, 2022. It is now read-only.

Commit

Permalink
feat: mocking ES6 imports when using Babelrc (#278)
Browse files Browse the repository at this point in the history
During test runs, there is a Babel plugin that transforms ES6 imports into plain objects that can be stubbed using [cy.stub](https://on.cypress.io/stub). In essence

```js
// component imports named ES6 import from "calc.js
import { getRandomNumber } from './calc'
const Component = () => {
  // then calls it
  const n = getRandomNumber()
  return <div className="random">{n}</div>
}
```

The test can mock that import before mounting the component

```js
import Component from './Component.jsx'
import * as calc from './calc'
describe('Component', () => {
  it('mocks call from the component', () => {
    cy.stub(calc, 'getRandomNumber')
      .as('lucky')
      .returns(777)
    mount(<Component />)
  })
})
```
  • Loading branch information
bahmutov authored Jun 10, 2020
1 parent d12ddcd commit 400f85d
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 32 deletions.
46 changes: 34 additions & 12 deletions docs/recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ See example in [bahmutov/Jscrambler-Webpack-React](https://github.com/bahmutov/J

## Your `.babelrc` file

If you are using Babel without Webpack to transpile, you can use the plugin that tells Babel loader to use your configuration file.
If you are using Babel without Webpack to transpile, you can use the plugin that tells Babel loader to use your `.babelrc` configuration file.

```js
// cypress/plugins/index.js
Expand All @@ -62,24 +62,46 @@ module.exports = (on, config) => {
}
```

**Bonus:** in order to enable code instrumentation, add the `babel-plugin-istanbul` (included in this plugin) to your `.babelrc` setup. You can place it under `test` environment to avoid instrumenting production code. Example `.babelrc` config file that you can execute with `BABEL_ENV=test npx cypress open`
### Add Babel plugins

If you want to use code instrumentation, add the [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul) to your `.babelrc` setup. You do not even need to install it separately, as it is already included in `cypress-react-unit-test` as a dependency.

If you want to use ES6 import mocking, add the [@babel/plugin-transform-modules-commonjs](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-modules-commonjs) to the list of plugins. This module is also included in `cypress-react-unit-test` as a dependency.

```json
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": [
"babel-plugin-istanbul",
[
"@babel/plugin-transform-modules-commonjs",
{
"loose": true
}
]
]
}
```

When loading your `.babelrc` settings, `cypress-react-unit-test` sets `BABEL_ENV` and `NODE_ENV` to `test` if they are not set already. Thus you can move the above plugins into the `test` environment to exclude them from being used in production bundle.

```json
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
{
"plugins": ["@babel/plugin-proposal-class-properties"]
},
"@emotion/babel-preset-css-prop"
],
"presets": ["@babel/preset-env", "@babel/preset-react"],
"env": {
"test": {
"plugins": ["babel-plugin-istanbul"]
"plugins": [
"babel-plugin-istanbul",
[
"@babel/plugin-transform-modules-commonjs",
{
"loose": true
}
]
]
}
}
}
```

See [bahmutov/react-loading-skeleton](https://github.com/bahmutov/react-loading-skeleton) example
See [examples/using-babel](examples/using-babel) folder for full example.
18 changes: 5 additions & 13 deletions examples/using-babel/.babelrc
Original file line number Diff line number Diff line change
@@ -1,27 +1,19 @@
{
"sourceType": "unambiguous",
"presets": [
"@babel/preset-env",
"@babel/preset-react",
// {
// "plugins": ["@babel/plugin-proposal-class-properties"]
// },
"@emotion/babel-preset-css-prop"
],
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": ["@babel/plugin-proposal-class-properties"],
"env": {
// TODO switch this to "development" name
"development": {
// place plugins for Cypress tests into "test" environment
// so that production bundle is not instrumented
"test": {
"plugins": [
// during Cypress tests we want to instrument source code
// to get code coverage from tests
// "babel-plugin-istanbul"
"babel-plugin-istanbul",
// we also want to export ES6 modules as objects
// to allow mocking named imports
[
"@babel/plugin-transform-modules-commonjs",
{
"allowCommonJSExports": true,
"loose": true
}
]
Expand Down
31 changes: 30 additions & 1 deletion examples/using-babel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ npm test

## Specs

See specs [src/Post.spec.js](src/Post.spec.js) and [src/Skeleton.spec.js](src/Skeleton.spec.js). The specs are bundled using [.babelrc](.babelrc) settings via [cypress/plugins/index.js](cypress/plugins/index.js) file that includes file preprocessor
See spec files [src/\*.spec.js](src). The specs are bundled using [.babelrc](.babelrc) settings via [cypress/plugins/index.js](cypress/plugins/index.js) file that includes file preprocessor

```js
// let's bundle spec files and the components they include using
Expand All @@ -27,3 +27,32 @@ module.exports = (on, config) => {
return config
}
```

## Mocking

During test runs, there is a Babel plugin that transforms ES6 imports into plain objects that can be stubbed using [cy.stub](https://on.cypress.io/stub). In essence

```js
// component imports named ES6 import from "calc.js
import { getRandomNumber } from './calc'
const Component = () => {
// then calls it
const n = getRandomNumber()
return <div className="random">{n}</div>
}
```

The test can mock that import before mounting the component

```js
import Component from './Component.jsx'
import * as calc from './calc'
describe('Component', () => {
it('mocks call from the component', () => {
cy.stub(calc, 'getRandomNumber')
.as('lucky')
.returns(777)
mount(<Component />)
})
})
```
5 changes: 5 additions & 0 deletions examples/using-babel/src/Component.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
import React from 'react'
import { getRandomNumber } from './calc'

/**
* Example React component that imports `getRandomNumber`
* function from another file and uses it to show a random
* number in the UI.
*/
const Component = () => {
const n = getRandomNumber()
return <div className="random">{n}</div>
Expand Down
3 changes: 3 additions & 0 deletions examples/using-babel/src/Component.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { mount } from 'cypress-react-unit-test'
import Component from './Component.jsx'
import * as calc from './calc'

// import the component and the file it imports
// stub the method on the imported "calc" and
// confirm the component renders the mock value
describe('Component', () => {
it('mocks call from the component', () => {
cy.stub(calc, 'getRandomNumber')
Expand Down
3 changes: 2 additions & 1 deletion examples/using-babel/src/ComponentReq.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/// <reference types="cypress" />
// component mixes `require` and `export` keywords
const React = require('react')
const { getRandomNumber } = require('./calc')

Expand All @@ -7,4 +8,4 @@ const Component = () => {
return <div className="random">{n}</div>
}

module.exports = Component
export default Component
2 changes: 1 addition & 1 deletion examples/using-babel/src/ComponentReq.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// <reference types="cypress" />
const React = require('react')
const { mount } = require('cypress-react-unit-test')
const Component = require('./ComponentReq.jsx')
const Component = require('./ComponentReq.jsx').default
const calc = require('./calc')

describe('Component', () => {
Expand Down
3 changes: 2 additions & 1 deletion examples/using-babel/src/Mock.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { mount } from 'cypress-react-unit-test'
import Post from './Post'
import * as calc from './calc'

// confirm the Post component that imports and calls the "getRandomNumber"
// renders the mock value because the test stubs it
describe('Mocking', () => {
// https://github.com/bahmutov/cypress-react-unit-test/issues/266
it('mocks import used by the Post', () => {
cy.stub(calc, 'getRandomNumber')
.as('lucky')
Expand Down
11 changes: 10 additions & 1 deletion examples/using-babel/src/Post.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,21 @@
import React from 'react'
import { mount } from 'cypress-react-unit-test'

import * as calc from './calc'
import SideBySide from './SideBySide'
import Post from './Post'
import { SkeletonTheme } from 'react-loading-skeleton'

// matches Post.story.js
describe('Post skeletons', () => {
it('mocks the es6 import', () => {
cy.stub(calc, 'getRandomNumber')
.as('lucky')
.returns(777)

mount(<Post />)
cy.contains('.random', '777')
})

it('default', () => {
mount(
<SideBySide>
Expand Down
13 changes: 11 additions & 2 deletions plugins/babelrc/file-preprocessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
const debug = require('debug')('cypress-react-unit-test')
const webpackPreprocessor = require('@cypress/webpack-preprocessor')
const { addImageRedirect } = require('../utils/add-image-redirect')
const babelCore = require('@babel/core')

// note: modifies the input object
function enableBabelrc(webpackOptions) {
Expand Down Expand Up @@ -43,7 +42,12 @@ function enableBabelrc(webpackOptions) {
module.exports = config => {
debug('env object %o', config.env)

const nodeEnvironment = 'development'
debug('initial environments %o', {
BABEL_ENV: process.env.BABEL_ENV,
NODE_ENV: process.env.NODE_ENV,
})

const nodeEnvironment = 'test'
if (!process.env.BABEL_ENV) {
debug('setting BABEL_ENV to %s', nodeEnvironment)
process.env.BABEL_ENV = nodeEnvironment
Expand All @@ -53,6 +57,11 @@ module.exports = config => {
process.env.NODE_ENV = nodeEnvironment
}

debug('environments %o', {
BABEL_ENV: process.env.BABEL_ENV,
NODE_ENV: process.env.NODE_ENV,
})

const coverageIsDisabled =
config && config.env && config.env.coverage === false
debug('coverage is disabled? %o', { coverageIsDisabled })
Expand Down

0 comments on commit 400f85d

Please sign in to comment.