Enable mocking a module in Cypress Component testing #16741
Replies: 16 comments 12 replies
-
Bump - we're encountering the same need here. We want to migrate away from jest, but jest includes super useful mocking tools built-in. We've attempted to use rewiremock to mock modules (since it appears its the only library that allows mocking in a webpack env) in our React components, but we're encountering the error from this stackoverflow Has anyone been able to get around this? |
Beta Was this translation helpful? Give feedback.
-
I'm having the same issue here where I can't easily stub using Workaround below I came up with but it's not as clean as the way jest mock modules. // component file
import { useToggle } from "./toggle";
export const App = () => {
const toggle = App.useToggle();
return (
<div className="App">
<h1>Toggle is {String(toggle)}</h1>
</div>
);
};
App.useToggle = useToggle;
// test file
it("stub works", () => {
cy.stub(App, "useToggle").returns(false);
mount(<App />);
cy.contains("Toggle is false");
}); |
Beta Was this translation helpful? Give feedback.
-
Bump, I'd also like to see this. |
Beta Was this translation helpful? Give feedback.
-
I need to write up a more detailed response to this and our stance on module-based mocking. Why is Cy different than Jest when mocking files?The technical reason file-based mocking and mocking esm functions is possible for Jest is that Jest rewrites the code of your Cypress works fine with CJS and code that uses Workaround: Put your esm code on an object and then mock the object.For folks looking for a workaround, @FelipeLahti's workaround is recommended, you'll need to use a default export or other object-like container and pass that into The reason this doesn't exist yet is that we haven't found it to be necessary. Initially, I thought it would be, but after building larger projects with it, we've found that many times it's not needed or there's a simpler way to accomplish the same thing. ExampleIn the following code block, we're looking at an App component that renders an h1 tag with "Toggle is (true or false)". // component file
import { useToggle } from "./toggle";
export const App = () => {
const toggle = App.useToggle();
return (
<div className="App">
<h1>Toggle is {String(toggle)}</h1>
</div>
);
}; To test this component, the test I'd write is: "When mounting the App, the value 'toggle is true' is shown by default". The first instinct people have is to say OK, but how do I change the value of the toggle to make sure that everything works? To which, I say: Well, how would your users do it? Are your users using the UI to affect state change (ui-based testing)? Are your users other developers that are using this component (prop-based testing) and want to make sure it updates correctly when props change?
The test we're writing is really about the App component responding to a toggle being updated somewhere and having a default value of (true or false). The fact that App is using On file-based mocking in generalJest's file-based mocking is generally out of necessity of preventing expensive bits of third party code from executing, however for a component to "look and feel" correct, having real dependencies is what you want 99% of the time. If you're depending on something like Material UI, you definitely want to render those components or else your app/components will look and behave incorrectly. Your code should be as production-like as possible. cy.interceptMaking things like external network requests and mocking responses is a common reason folks like to file mock. Instead of mocking at the file-level, we suggest mocking at the environment level and use tools like So, TLDR
Finally, tell us your use-caseWe obviously don't speak for a majority of developers, and Cypress Component Testing's API is still in its early days. We've been able to avoid module-based mocking and stubbing completely in our usages of Cypress Component Testing with close to 0 pain. That does not mean a first-class API isn't warranted. I'd like to keep this discussion going and understand what kinds of use cases require file-based mocks. Please share :-) |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
@JessicaSachs - what do you recommend with respect to component tests in Cypress? I'm currently trying to figure out the solution to a broken module import in a component test. This could well be an issue in my configuration (getting Cypress and Sveltekit to play nicely together is proving interesting 😅); but in the context of a component test I don't actually need the method from this module to be imported. In fact it's something that it would make more sense to mock and spy on. This feels like a situation in which mocking module imports might make sense. |
Beta Was this translation helpful? Give feedback.
-
Sometimes, we need mock module which from dependencies |
Beta Was this translation helpful? Give feedback.
-
Im a little late to the party here - while I agree, we should mock as little as possible - sadly, mocking is an integral part of unit/integration testing (atleast it is for us). Mocking helps us remove complicated external functionality which is otherwise not relevant to the functionality that we might want to test out inside of a React component (which helps keep our tests light, and easier to read and understand - because mocking (module rewiring) can save us from needing a slew of I've long struggled with this within cypress component testing (cypress uses sinon under the hood, which is NOT a module rewirer, like jest's https://github.com/asapach/babel-plugin-rewire-exports this little babel plugin works a treat (as long as you still use babel to compile, and not some fancy rust-based compiler like SWC). The other nice thing about this tooling, is I find the rewiring syntax incredibly simple compared to jest's convoluted (IMO) module rewiring functionality and (ridiculous mock variable naming) rules. here's some demo code which i make available to other engineers in my team, who ask me how module rewiring can work inside of cypress: Rewirable imports from an NPM library: // ./rewirable-imports.ts
import { SimpleText } from '@imtbl/design-system';
export { SimpleText }; The test component: import { css, cx } from '@emotion/css';
import { SimpleText } from './rewirable-imports'; // @NOTE SimpleText is a component taken from an npm library
import { useSimpleHook } from './simple-number.hook';
interface MooPropTypes {
name: string;
}
export const demoFunction = () => 'how now, brown';
export const demoObject = {
moo: 'cow',
};
export const demoString = 'i wish i could be rewired ...';
export function Moo({ name }: MooPropTypes) {
const { number } = useSimpleHook();
return (
<>
<div
className={cx(
'moo',
css`
background: gold;
`,
)}
>
{name} {demoFunction()} {number} {JSON.stringify(demoObject)}{' '}
{demoString}
</div>
<SimpleText>demo body text here</SimpleText>
</>
);
}
The test which does some rewiring: import { mount } from '@cypress/react';
import { cy, describe, it } from 'local-cypress';
import * as MooFile from './Moo.component';
import { Moo } from './Moo.component';
import * as RewirableImports from './rewirable-imports';
import * as SimpleNumberHook from './simple-number.hook';
const { rewire$demoFunction, rewire$demoObject, rewire$demoString } =
MooFile as any;
const { rewire$useSimpleHook } = SimpleNumberHook as any;
const { rewire$SimpleText } = RewirableImports as any;
describe('rewire tests', () => {
it('test out rewire', () => {
// @NOTE: rewire various things:
rewire$demoFunction(() => 'holy smokes batman!');
rewire$useSimpleHook(() => ({ number: 48 }));
rewire$demoObject({ aww: 'yeah!' });
rewire$demoString('i can totally rewire');
rewire$SimpleText(() => <section>this is a stub DS component</section>);
mount(<Moo name="POW!" />);
cy.contains('48').should('exist');
cy.contains('this is a stub DS component').should('exist');
cy.contains('i can totally rewire').should('exist');
});
}); Its also worth adding, that I have just recently migrated a tonne of cypress 9.x component tests across to cypress 10.x and everything still works nicely. SIDENOTE: thank you to the cypress team for the amazing 10.x release. It is waaayyy nicer than i was expecting. Definitely a HUGE improvement! :D |
Beta Was this translation helpful? Give feedback.
-
Well, unfortunately, after a full week of trying lots of approaches, I am giving up on working around the lack of proper stubs on |
Beta Was this translation helpful? Give feedback.
-
I can't think why I would want to mock the child components in a parent, but I would also like to see it work. No issues with with mocking functions in another component. When mocking the children, the mock doesn't seem to have an effect. Here's a WIP PR with reproducible code. |
Beta Was this translation helpful? Give feedback.
-
Just want to agree that mocking is critical for testing code. Especially old legacy code that you're going to need to refactor... which is exactly the kind of code you REALLY need to write tests for. You're right that architecting your code so mocks aren't needed is best. But we know that. Reality is messy and we need a testing tool for reality. please and thanks! |
Beta Was this translation helpful? Give feedback.
-
For people who are still looking for this feature: mocking of modules and dependencies just landed in WebdriverIO with a similar syntax as used in Vitest or Jest: https://webdriver.io/docs/component-testing/mocking. |
Beta Was this translation helpful? Give feedback.
-
We are working on finding an adequate solution for this problem. It's a very difficult technical problem to solve but we hope to provide some solutioning in the relatively near future. |
Beta Was this translation helpful? Give feedback.
-
+1 to this as I recently came across a big use case when looking into In general, I am a huge proponent of behavioral testing and mocking as little as possible. However, I have found that it is often extremely convenient, if not necessary, to be able to pre-populate context providers or global state providers with specific values in order to test a component that relies on that state in isolation. If I'm using a component-scoped state management solution like React Context or Redux, this is easy to do in component tests. However, our team has recently been looking into module-scoped state management solutions like I'm curious if anyone else has had success testing applications that use |
Beta Was this translation helpful? Give feedback.
-
Just adding a use case here (and hopefully someone will know a good workaround) for a scenario which cy.intercept wouldn't be able to fix. We have a component dependent on the url's query params. We use the 'useSearchParam' from next/navigate and without mocking, solutions will be a bit hacky to say the least. |
Beta Was this translation helpful? Give feedback.
-
A solution of stubbing a module comes from https://glebbahmutov.com/blog/stub-react-import/ TL;DR
const { defineConfig } = require("cypress");
module.exports = defineConfig({
component: {
devServer: {
framework: "create-react-app",
bundler: "webpack",
webpackConfig: {
mode: "development",
devtool: false,
module: {
rules: [
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
"@babel/preset-env",
["@babel/preset-react", { runtime: "automatic" }],
],
plugins: [
[
"@babel/plugin-transform-modules-commonjs",
{ loose: true },
],
],
},
},
},
],
},
},
},
},
});
import React from "react";
import App from "./App";
import * as Utils from "./utils";
describe("<App />", () => {
it("renders", () => {
cy.stub(Utils, "getToken").returns("Mock token"); // stub the return value
cy.mount(<App />);
});
});
import logo from "./logo.svg";
import "./App.css";
import { getToken } from "./utils";
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
{/* Display token value */}
<p>{getToken()}</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
export const getToken = () => {
return "Default token";
}; ![]() Minimal repo setup to try out:Create React App: Go to directory and install packages: Add Cypress npm script {
"name": "cypress-playground",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"cy:open": "cypress open"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.23.0",
"@babel/preset-env": "^7.22.20",
"@babel/preset-react": "^7.22.15",
"cypress": "^13.3.0"
}
} Open Cypress: Click through the Cypress UI to add first test:
Follow the steps in TL;DR section above ⬆️ |
Beta Was this translation helpful? Give feedback.
-
Hi everyone,
In our project we're using React Micro Frotends with single-spa framework. So, we've created a utility library that has all the components residing in it, and exports them so that they can be reused across multiple other Micro Frontends wherever required. I have started adding cypress component tests to this MFE-Components application using mount import.
I'm sharing the reproducible github repository link, please access them from here:
https://github.com/aaaaronmiles/MFE-App1
https://github.com/aaaaronmiles/MFE-App2
These 2 are some sample MFE applications that takes Button exported from MFE-ComponentUtil
https://github.com/aaaaronmiles/CypressComponentTesting
Here, there are 3 MFEs in it, MFE-RootConfig is the container, MFE-CommonUtil is the utility that has common methods exported, MFE-ComponentUtil is the Utility module, our area of interest, where I've to add cypress component tests for each component in it. I've already added a spec for Button component that imports 'isHidden' member from 'MFE-CommonUtil'.
You can run 5 of these MFEs locally by issuing npm start command followed by npm install, to execute them in action.
Here I wanted to mock 'isHidden' in tests. How can I achieve this mocking in unit tests.
But now I'm facing an issue where it imports a method from other utility module that is not available in MFE-Components and is throwing an error as shown in the below screenshot, when attempted to add tests. So I'm thinking of mocking it up somehow in cypress but I didn't find any related resources.
Question: I wanted something like moduleNameMapper available in Jest to mock out an entire module in cypress as well. Is there any such provision for us in cypress to stub out an entire module and simulate what it should return when it's called from tests. And whenever tests are running, the corresponding call showld be resolved to the mocked module instead of the original method implementation. Will cy.stub() help me in any way?
This is how my import in the component to be tested looks like (from external resource):
import { commonMethod } from "@org/commonUtil";
Appreciate your help, any thoughts are welcomed. Thanks for your time.
Beta Was this translation helpful? Give feedback.
All reactions