How to Stub a module in Cypress - javascript

I'm trying to stub a module using Cypress. Here's what I've tried so far, but is not working.
This is the short version of my component/page
// SomeComponent.jsx
import { useSomething } from './useSomething'
const SomeComponent = () => {
// useSomething is a custom hook
const { data, error } = useSomething()
const renderData = () => {
// map the data into an array of JSX elements
return data.map(...)
}
return (
<div>
{renderData()}
</div>
)
}
export default SomeComponent
Here's how my custom hook looks like
// useSomething.js
import { useState } from 'react'
import { getData } from './db'
export const useSomething = () => {
const [data, setData] = useState({})
const [error, setError] = useState()
useEffect(() => {
getData().then(data => {
setData(data)
}).catch(err => {
setError(error)
})
// ... some other unrelated code here
}, [])
return { data, error }
}
Here's how getData looks like
// getData.js
export const getData = () => {
const data = // some API call from external services
return data
}
The method is exposed via db.js (actually db/index.js)
// db.js
export * from './getData'
I'm trying to stub the getData.js to make the e2e test more consistent. Here's what I did.
// something.spec.js
// I'm writing #src just to keep the code sample here short, it's the same file as the db.js I write above
import * as db from '#src/db'
...
// this is how I try to do the stubbing
cy.stub(db, 'getData').resolves(something)
...
The stubbing above doesn't work. The API call to the external service is still happening when running the test. The documentation itself leads me to deduce that I should write it this way, but it's not working.

You can expose db on the window
// useSomething.js
import { useState } from 'react'
import * as db from './db'
const { getData } = db;
if (window.Cypress) { // only when testing
window.db = db;
}
and in the test
cy.window().then(win => {
cy.stub(win.db, 'getData').resolves(something);
})
Or use intercept to stub the API call.

Related

Issue in Shared code not working in React JS

I want to integrate Shared code between React Native and React JS so I create one module which is uploaded on Gitlab and I gave path to my React JS project as below. First of all refer my project structure
In gitmodule it is like below image
In My App.js file i want to access service--> api.ts file so I gave path to my App.js file as below
import {getUserData} from '../src/rn_sharecodecommon/Service/api'
const { isLoading, data, message, getUser } = getUserData()
useEffect(()=>{
getUser('users')
},[])
and My Service/api.ts file code as below
import { useState } from "react";
import axios from "axios";
export const getUserData = () => {
const [isLoading, setIsLoading] = useState(false);
const [message, setMessage] = useState('');
const [data, setData] = useState({});
const getUser = async (url) => {
console.log("URL-->", url);
let res;
try {
console.log("URL--> 1");
setIsLoading(true);
axios.get('https://api.github.com/'+url).then((response) => {
res = response.data;
setIsLoading(false);
setData(response.data);
console.log("API lOfi",response.data);
});
} catch (error) {
console.log("URL--> 2");
setIsLoading(false);
setMessage(error);
}
};
return {
isLoading,
message,
data,
getUser,
};
}
When i run above code in React JS it is showing me error like below
Can't resolve '../src/rn_sharecodecommon/Service/api' in '/Volumes/Data/WebProject/SSCode/sharecode/src'
Any Idea how can i solve this error?
I just resolved by make small change in import as below
import { getUserData } from './rn_sharecodecommon/Service/api.ts'

how to check axios get function called or not in react js?

I am trying to test below function or in other words I am trying to write unit test cases of below function.But I am getting error _axios.default.get.mockResolvedValueOnce is not a function
import React from "react";
import axios from "axios";
export default () => {
const [state, setState] = React.useState([]);
const fetchData = async () => {
const res = await axios.get("https://5os4e.csb.app/data.json");
setState(res.data);
};
React.useEffect(() => {
(async () => {
await fetchData();
})();
}, []);
return [state];
};
here is my code
https://codesandbox.io/s/awesome-jepsen-5os4e?file=/src/usetabData.test.js
I write unit test case like that
import useTabData from "./useTabData";
import { act, renderHook, cleanup } from "#testing-library/react-hooks";
import mockAxios from "axios";
describe("use tab data", () => {
afterEach(cleanup);
it("fetch tab data", async () => {
mockAxios.get.mockResolvedValueOnce({
data: {
name: "hello"
}
});
await act(async () => renderHook(() => useTabData()));
expect(mockAxios.get).toHaveBeenCalled();
});
});
Code sandbox doesn't support manual mocks as far as I know.
However, your __mock__ is placed in wrong directory structure. It should be a sibling of node_module.
Having said that, easiest way is to use https://github.com/ctimmerm/axios-mock-adapter
import useTabData from "./useTabData";
import { act, renderHook, cleanup } from "#testing-library/react-hooks";
import axios from "axios";
import MockAxiosAdapter from "axios-mock-adapter";
const mockAxios = new MockAxiosAdapter(axios);
afterEach(() => {
cleanup();// this is not needed . its done by testing library after each.
mockAxios.reset();
});
describe("use tab data", () => {
it("fetch tab data", async () => {
mockAxios.onGet(200, { data: { test: "123" } }); // response code and object.
const { result, waitForNextUpdate } = renderHook(() => useTabData());
const [value] = result.current;
// assert value
// assert the axios call by using history object
});
});
You can use history to assert: https://github.com/ctimmerm/axios-mock-adapter#history

Async realtime updates with Firestore issue

I try to get realtime data back from Firestore with React-Native.
I have a helpers.js file where I store and export all the get and set functions for Firestore and I just import them in App.js to use it.
When I try to get only once (no realtime), it works perfectly.
This is working:
In helpers.js:
const waitlistRef = db.firestore().collection('waitlist');
export async function getWaitlist() {
let waitlist = [];
await waitlistRef.get().then(res => {
res.forEach(doc => {
waitlist.push(doc.data());
});
});
return waitlist;
}
In App.js:
import { getWaitlist } from './helpers';
...
useEffect(() => {
getWaitlist().then(waitlist => console.log(waitlist));
}, []);
So it works, but I only can get it once (no realtime). In order to get realtime updates, I've tried this method:
In helpers.js:
const waitlistRef = db.firestore().collection('waitlist');
export async function getWaitlist() {
let waitlist = [];
await waitlistRef.onSnapshot(snapshot => {
snapshot.forEach(doc =>
waitlist.push(doc.data()),
);
});
return waitlist;
}
In App.js:
import { getWaitlist } from './helpers';
...
useEffect(() => {
getWaitlist().then(waitlist => console.log(waitlist));
}, []);
But when I try to log the waitlist array which was supposed to be returned from the helper function, I just get an empty array.
Any ideas?

How to test componentDidMount conditionally making API calls

I'm using React in my application. I'm making an API call in my componentDidMount but it is conditional. My code in component is
componentDidMount() {
if (!this.props.fetch) {
fetchAPICall()
.then(() => {
/** Do something **/
});
}
}
I've written test as :
it('should not fetch ', () => {
const TFCRender = mount(<Component fetch />);
const didMountSpy = jest.spyOn(TFCRender.prototype, 'componentDidMount');
expect(didMountSpy).toHaveBeenCalledTimes(1);
expect(fetchAPICall).toHaveBeenCalledTimes(0);
});
The test is throwing me error as
TypeError: Cannot read property 'componentDidMount' of undefined
What am I doing wrong and what is the right way to test such case.
From the official docs, you need to spy the component before mounting it.
Following is a working example that I have created with create-react-app. I've also added some comments in the example code:
App.js
import { fetchAPICall } from './api';
class App extends Component {
componentDidMount() {
if (!this.props.fetch) {
fetchAPICall().then(console.log);
}
}
render() {
return <div>Testing the result</div>;
}
}
export default App;
api.js
export const fetchAPICall = () => {
return Promise.resolve('Getting some data from the API endpoint');
};
App.test.js
import Component from './App';
import * as apis from './api'; // assuming you have a separate file for these APIs
// Mock the fetchAPICall, and since the data fetching is asynchronous
// you have to mock its implementation with Promise.resolve()`
apis.fetchAPICall = jest.fn(() => Promise.resolve('test'));
describe('spyOn', () => {
let didMountSpy; // Reusing the spy, and clear it with mockClear()
afterEach(() => {
didMountSpy.mockClear();
});
didMountSpy = jest.spyOn(Component.prototype, 'componentDidMount');
test('should not fetch ', () => {
// Ensure the componentDidMount haven't called yet.
expect(didMountSpy).toHaveBeenCalledTimes(0);
const TFCRender = mount(<Component fetch />);
expect(didMountSpy).toHaveBeenCalledTimes(1);
expect(apis.fetchAPICall).toHaveBeenCalledTimes(0);
});
test('should fetch', () => {
expect(didMountSpy).toHaveBeenCalledTimes(0);
const TFCRender = mount(<Component fetch={false} />);
expect(didMountSpy).toHaveBeenCalledTimes(1);
expect(apis.fetchAPICall).toHaveBeenCalledTimes(1);
});
});
Not sure if this is the best practice, but this is how I usually write my own tests.
Hope this help!

Mounting a vue instance with avoriaz, but cannot sinon spy on imported function

I have the following component script (some irrelevant bits removed):
import api from '#/lib/api';
export default {
methods: {
upload (formData) {
api.uploadFile(formData).then(response => {
this.$emit('input', response.data);
});
}
}
};
And I have the following test, which uses avoriaz to mount the Vue instance:
import { mount } from 'avoriaz';
import { expect } from 'chai';
import sinon from 'sinon';
import UploadForm from '#/components/UploadForm';
describe('upload', () => {
it('passes form data to api.uploadFile', () => {
const testFormData = { test: 'test' };
const api = {
uploadFile: sinon.spy()
};
const wrapper = mount(UploadForm);
wrapper.vm.api = api;
wrapper.vm.upload(testFormData);
expect(api.uploadFile.called).to.equal(true);
});
});
My sinon spy is never called, and I've tried a couple different variations on the above. What is the best way to spy on an imported function like this? Or am I conceptually approaching this the wrong way?
Problem
You need to stub the api dependency, which is a dependency of the file. This can't be done through the vue instance, since api is not a part of the vue component.
You need to stub the file dependency.
Solution
One method to do this is to use inject-loader.
Steps
Install inject-loader
npm install --save-dev inject-loader
At the top of your file, import UploadForm with inject-loader and vue-loader:
import UploadFormFactory from '!!vue-loader?inject!#/components/UploadForm';
This is a factory function that returns UploadForm with dependencies stubbed.
Now, in your test you need to call UploadFormFactory with the dependency you want stubbed:
const api = {
uploadFile: sinon.spy()
};
const UploadForm = UploadFormFactory({
'#/lib/api': api
})
So your test file will look like:
import { mount } from 'avoriaz';
import { expect } from 'chai';
import sinon from 'sinon';
import UploadFormFactory from '!!vue-loader?inject!#/components/UploadForm';
describe('upload', () => {
it('passes form data to api.uploadFile', () => {
const api = {
uploadFile: sinon.spy()
};
const UploadForm = UploadFormFactory({
'#/lib/api': api
})
const testFormData = { test: 'test' };
const api = {
uploadFile: sinon.spy()
};
const wrapper = mount(UploadForm);
wrapper.vm.upload(testFormData);
expect(api.uploadFile.called).to.equal(true);
});
});
More info
I've written a tutorial with more detail here - https://www.coding123.org/stub-dependencies-vue-unit-tests/
I think Edd's answer is the most encompassing for most scenarios, so I'm marking his as the accepted answer. However, the workaround I came up with was to make the api library a global service (Vue.prototype.$api = api) in my main.js file, and then overwrite the global with a stub before each test.
describe('UploadForm.vue', () => {
let wrapper;
const uploadFile = sinon.stub().returns(Promise.resolve({ data: 0 }));
beforeEach(() => {
wrapper = mount(UploadForm, {
globals: {
$api: { uploadFile }
}
});
});
// ...

Categories