cypress-io / cypress

Fast, easy and reliable testing for anything that runs in a browser.
https://cypress.io
MIT License
47.02k stars 3.18k forks source link

cy.stub breaks after upgrade to 7.3.0 #16532

Closed kevzettler closed 1 year ago

kevzettler commented 3 years ago

Current behavior

I am migrating a project from 6.5.0 to 7.3.0 The project has a large test suite. After installing 7.3.0 many previously successful tests are failing. After investigation it appears that the behavior of cy.stub has changed.

The existing tests have many 'module' stubs like:

const AuthProvider = require('../../../../src/providers/Auth'); 
cy.stub(AuthProvider, 'login', (email, password, callback) => {
    callback("hard value", undefined)
})

When the test is run this stub never gets called and the default AuthProvider.login function gets called. So the stub does nothing.

this stack overflow question demonstrates the same issue https://stackoverflow.com/questions/61944433/how-to-stub-a-module-function-with-cypress

Desired behavior

The behavior should be consistent with 6.5.0 I'm not seeing any documentation on this breaking change to cy.stub or how to stub modules like this.

Versions

$ node -v v10.23.0 $ npm -v 6.14.8 $ cypress -v Cypress package version: 7.3.0 Cypress binary version: 7.3.0 Electron version: 12.0.0-beta.14 Bundled Node version: 14.15.1

lmiller1990 commented 3 years ago

cy.stub has (or certainly should not) have changed between 6.5 and 7.3. Is it consistently broken, or just for some examples? If you can provide a minimal repository showing the issue, that would be great.

kevzettler commented 3 years ago

To further clarify this is happening specifically in component tests. and these component tests are also in a migration from "cypress-react-unit-test": "^4.17.2", to "@cypress/react": "^5.6.0"

I suspect that the mount function for component tests is different. Seems the component tests are now mounted in a separate scope and do not share scope with the stubbed modules. I'll try making a minimal repo but may take some time

lmiller1990 commented 3 years ago

Knowing it's with the component testing runner is useful information. The mount function is the same, but the underlyling architecture changed (from webpack-preprocessor to webpack-dev-server). That might have introduced the regression.

Can you share the contents of ../../../../src/providers/Auth? That should be enough to go on. For now I'll try to reproduce it with the information here.

lmiller1990 commented 3 years ago

I tried reproducing as best I could: see here. I could not reproduce - but it's likely the problem is project configuration. In my case, the stub was called.

When I copy pasted your example, my IDE gave me deprecation error around the cy.stub syntax. I changed it to what was recommended. Here's my full test code:

const React = require('react')
const { mount } = require('@cypress/react')
const { CyStub } = require('./CyStub.jsx')
const AuthProvider = require('./auth.js')

describe('cy.stub', () => {
  let calls = 0

  beforeEach(() => {
    cy.stub(AuthProvider, 'login').callsFake(() => {
      calls += 1
    })
  })

  it('should stub commonjs require', () => {
    mount(<CyStub />)
    cy.get('button').click().then(() => {
      expect(calls).to.eq(1)
    })
  })
})

If you can provide a minimal reproduction of the problem you are having, I can take a look.

FelipeLahti commented 3 years ago

Hi @lmiller1990 Here's a reproducible example: https://github.com/FelipeLahti/cypress-react-component-example/commit/7e7136dfb05fd0eac03f2f8438ff1e372aafddef#diff-627a97a066b275dbbf813f6dafd9c9650279f8f3eb66d1a3a23cd62016cfc7b8

It's a fork from https://github.com/bahmutov/cypress-react-component-example

Stubbing the function calculate doesn't work using create react app.

//App.js
import { calculate } from './anModule';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>{calculate()}</p>
      </header>
    </div>
  );

//App.spec.js
import * as anModule from './anModule'

it('renders total is 10', () => {
  mount(<App />)
  cy.contains(/total is 10/i)
})

it('renders total is mocked value', () => {
  cy.stub(anModule, 'calculate').returns('total is 25')
  mount(<App />)
  cy.contains(/total is 25/i)
})
p-christ commented 3 years ago

i am also seeing a similar problem and have documented it here

andymerskin commented 2 years ago

I tried following this official example for mocking imports by adding @babel/plugin-transform-modules-commonjs in my Craco configuration on CRA 5, to allow module exports to be overwritten by stubs:

// craco.config.js
module.exports = {
  babel: {
    plugins: [
      [
        '@babel/plugin-transform-modules-commonjs',
        {
          loose: true,
        },
      ],
    ],
  },
};

And ended up getting this error for basically every module in my application:

Module not found: Error: You attempted to import /ExampleProject/node_modules/@babel/runtime/helpers/interopRequireDefault.js which falls outside of the project src/ directory. Relative imports outside of src/ are not supported.
You can either move it inside src/, or add a symlink to it from project's node_modules/.

Link to example: https://github.com/cypress-io/cypress/tree/master/npm/react/cypress/component/advanced/mocking-imports

lmiller1990 commented 2 years ago

There error you are describing is actually a limitation of Create React App, see here for more. They talk about the ModuleScopePlugin here.

We actually modify the webpack config on the fly to support importing from cypress directories, that happens here.

We definitely need to investigate how we can handle stubbing ES modules. Transpiling to commonjs is one approach, I don't think it's a good long term solution, though. That said, if you do keep going down this route and remove that CRA limitation, you should be able to get this to work.

rockindahizzy commented 1 year ago

Closing this issue. Please follow https://github.com/cypress-io/cypress/issues/22355 for updates on ES module mocking.