I created this hook:
import { useQuery, gql } from '#apollo/client';
export const GET_DECIDER = gql`
query GetDecider($name: [String]!) {
deciders(names: $name) {
decision
name
value
}
}
`;
export const useDecider = (name) => {
const { loading, data } = useQuery(GET_DECIDER, { variables: { name } });
console.log('loading:', loading);
console.log('data:', data);
return { enabled: data?.deciders[0]?.decision, loading };
};
Im trying to test it with react testing library:
const getMock = (decision) => [
{
request: {
query: GET_DECIDER,
variables: { name: 'FAKE_DECIDER' },
},
result: {
data: {
deciders: [{ decision }],
},
},
},
];
const FakeComponent = () => {
const { enabled, loading } = useDecider('FAKE_DECIDER');
if (loading) return <div>loading</div>;
console.log('DEBUG-enabled:', enabled);
return <div>{enabled ? 'isEnabled' : 'isDisabled'}</div>;
};
// Test
import React from 'react';
import { render, screen, cleanup, act } from '#testing-library/react';
import '#testing-library/jest-dom';
import { MockedProvider } from '#apollo/client/testing';
import { useDecider, GET_DECIDER } from './useDecider';
describe('useDecider', () => {
afterEach(() => {
cleanup();
});
it('when no decider provided - should return false', async () => {
render(<MockedProvider mocks={getMock(false)}>
<FakeComponent />
</MockedProvider>
);
expect(screen.getByText('loading')).toBeTruthy();
act((ms) => new Promise((done) => setTimeout(done, ms)))
const result = screen.findByText('isDisabled');
expect(result).toBeInTheDocument();
});
});
I keep getting this error:
Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error:
Related
I'm trying to execute a query when an array gets to be updated, but the query executes immediately!
So I have checkedProducts array I push some data into it when the screen mount, so my goal is to execute the query when this array has data.
here's the hook
import {useQuery} from 'react-query';
import {checkAvailableProducts} from '../Api';
export type checkProductItem = {
product_id: number;
detail_id: number;
};
export interface checkProducts {
products: checkProductItem[];
}
export const useCheckAvailableProducts = (products?: checkProducts) => {
console.log('products-RQ', products);
return useQuery(
['checkAvailableProducts'],
() => checkAvailableProducts(products),
{
enabled: !!products?.products, // OR products?.products.length > 0 not works
},
);
};
component
....
const [checkedProducts, setCheckedProducts] = useState<checkProducts>();
const {data: unavailableProducts, refetch} = useCheckAvailableProducts(
checkedProducts,
);
React.useEffect(() => {
if (cartProducts.length) {
let productsIDs = cartProducts.map(prod => {
return {
product_id: prod.id,
detail_id: prod.detail_id,
};
});
Promise.all(productsIDs).then(allProductsIDs => {
console.log('allProductsIDs', allProductsIDs);
return setCheckedProducts({
products: allProductsIDs,
});
});
}
}, [cartProducts]);
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
refetch();
console.log('unavailableProducts:', unavailableProducts);
if (unavailableProducts && unavailableProducts.length > 0) {
console.log('hey if0update!!!');
updateCart(unavailableProducts);
}
});
// Return the function to unsubscribe from the event so it gets removed on unmount
return unsubscribe;
}, [navigation, refetch, unavailableProducts, updateCart]);
I am trying to test the async fetch() function of my Vue Component. This method uses some of the props to build a query to use with our Apollo GQL instance. I'm having a hard time finding examples using a similar configuration that I am using.
Ultimately, I'd love to test the full query (building it and the return data) but if it is difficult to test the full return data, then I can settle with just testing the building process of the variables and hopefully the schema of the return data.
// ProductList.spec.js
import { shallowMount } from '#vue/test-utils'
import ProductListing from '~/components/blocks/ProductListing.vue'
import PRODUCT_LISTING_BLOCK_QUERY from '~/queries/productListingBlock.gql'
import '#testing-library/jest-dom'
const directives = {
interpolation: () => {}
}
const mocks = {
$md: {
render: value => value
}
}
describe('ProductListing', () => {
test('fetch from apollo', () => {
const testVariables = {
maxItems: 10,
matchSkus: 'ms239,mk332,as484',
matchTags: 'tag1,tag2,tag3',
matchCategories: '111,222,333,444'
}
const wrapper = shallowMount(ProductListing, {
propsData: testVariables,
stubs: ['ProductTileList'],
directives,
mocks
})
const client = this.$apollo.provider.clients.defaultClient // get an error on this line saying $apollo is undefined
const products = await client
.query({
PRODUCT_LISTING_BLOCK_QUERY,
testVariables,
context: {
clientName: 'powerchord'
}
})
.then(r => r.data.allProducts)
// const promise = wrapper.vm.fetch() // this doesn't seem to work/call my function
})
})
// ProductList.vue
<template lang="pug">
.mb-8(:id='elementId')
ProductTileList(:products="products")
</template>
<script lang="ts">
import Vue from 'vue'
import BaseBlockMixin from './BaseBlockMixin'
import query from '~/queries/productListingBlock.gql'
import { IObjectKeys } from '~/types/common-types'
import mixins from '~/utils/typed-mixins'
const Block = Vue.extend({
props: {
matchCategories: {
type: String,
default: ''
},
matchSkus: {
type: String,
default: ''
},
matchTags: {
type: String,
default: ''
},
maxItems: {
type: Number,
default: 20
}
},
data () {
return {
products: []
}
},
async fetch () {
const client = this.$apollo.provider.clients.defaultClient
const variables: IObjectKeys = {
limit: this.maxItems
}
if (this.matchSkus.length) {
variables.skus = this.matchSkus.split(',')
}
if (this.matchTags.length) {
variables.tags = this.matchTags.split(',')
}
if (this.matchCategories.length) {
variables.categories = this.matchCategories.split(',')
.map(c => parseInt(c))
.filter(c => !isNaN(c))
}
this.products = await client
.query({
query,
variables,
context: {
clientName: 'some-company'
}
})
.then((r: any) => r.data.allProducts)
}
})
export default mixins(Block).extend(BaseBlockMixin)
</script>
I have a component that waits until some data from a resource. I'm using React suspense to show the loading screen as until it gets the response. Everything work as expected.
However when testing, even though onGet is registered, in axiosMock, it never gets the request from <Cmp /> component. Test fails due to connection error.
import { render, waitFor } from '#testing-library/react';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import React, { Suspense } from 'react';
const axiosMock = new MockAdapter(axios, { onNoMatch: 'throwException' });
const wrapPromise = (promise) => {
let status = 'pending';
let result: any;
promise.then(
(r) => {
status = 'success';
result = r;
},
(e) => {
status = 'error';
result = e;
}
);
return {
read() {
return {
status,
result,
};
},
};
};
const fetchData = () => {
return wrapPromise(axios.get('/test').then((r) => r.data));
};
const resource = fetchData();
export const Cmp = () => {
const str = resource.read();
return <h1>{str}</h1>;
};
describe('Main Tests', () => {
beforeAll(() => {
axiosMock.reset();
});
/*
THIS WORKS
*/
it('Sample Request Test', async () => {
axiosMock.onGet('/test').reply(200, 'hello');
expect(await axios.get('/test').then((r) => r.data)).toBe('hello');
});
/*
THIS DOES NOT WORK
*/
it('Component Request Test', async () => {
axiosMock.onGet('/test').reply(200, 'hello');
const { getByText } = render(
<Suspense fallback={<p>Loading...</p>}>
<Cmp />
</Suspense>
);
await waitFor(() => {
return getByText('hello');
});
});
});
I am trying to solve how to rerender a different functional component.
onComplete: function (data) {
console.log("everything is complete");
console.log("this is the applicant id", id);
let obj;
axios
.post("http://localhost:5000/post_id", {
applicant_id: id,
})
.then((response) => {
obj = response.data.data.data.json_data.result;
});
setTimeout(() => {
if (obj === "clear") {
onfidoOut.tearDown(); // <- the Onfido view did not unmount
renderResult(); // <- BAD APPROACH? `renderResult` renders a HTML node instead of triggers a view rerender.
}
}, 3000);
},
I embarked upon an approach of rendering an HTML node instead of triggering a view rerender because I have never implemented a view rerender to a different component outside of an authentication flow in a class-based component. Also this is a microfrontend application. This is the whole file from where the snippet above is obtained:
const useOnfidoFetch = (URL) => {
const [token, setToken] = useState();
const [id, setId] = useState();
useEffect(() => {
axios
.get("http://localhost:5000/post_stuff")
.then((response) => response.data.data.data.json_data)
.then((json_data) => {
console.log("this is the json data", json_data);
const id = json_data.applicant_id;
const token = json_data.onfido_sdk_token;
setId(id);
setToken(token);
});
}, [URL]);
useEffect(() => {
if (!token) return;
console.log("this is working!");
OnfidoSDK.init({
token,
containerId: "root",
steps: [
{
type: "welcome",
options: {
title: "Open your new bank account",
},
},
"document",
],
onComplete: function (data) {
console.log("everything is complete");
console.log("this is the applicant id", id);
let obj;
axios
.post("http://localhost:5000/post_id", {
applicant_id: id,
})
.then((response) => {
obj = response.data.data.data.json_data.result;
});
setTimeout(() => {
if (obj === "clear") {
onfidoOut.tearDown();
renderResult();
}
}, 3000);
},
});
}, [id, token]);
function renderResult() {
return <Redirect to="/result" />;
}
};
export default function() {
const URL = `${transmitAPI}/anonymous_invoke?aid=onfido_webapp`;
const result = useOnfidoFetch(URL, {});
return (
<div id={onfidoContainerId} />
)
}
And this is the bootstrap.js file:
import React from 'react';
import ReactDOM from 'react-dom';
import {createMemoryHistory, createBrowserHistory} from 'history';
import App from './App';
// Mount function to start up the app
const mount = (el, { onNavigate, defaultHistory, initialPath}) => {
const history = defaultHistory || createMemoryHistory({
initialEntries: [initialPath],
});
if (onNavigate) {
history.listen(onNavigate);
}
ReactDOM.render(<App history={history} />, el);
return {
onParentNavigate({ pathname: nextPathname }) {
const {pathname} = history.location;
if (pathname !== nextPathname) {
history.push(nextPathname);
}
},
};
};
// If I am in development and isolation
// call mount immediately
if (process.env.NODE_ENV === 'development') {
const devRoot = document.querySelector('#_marketing-dev-root');
if (devRoot) {
mount(devRoot, {defaultHistory: createBrowserHIstory()});
}
}
// Assuming we are running through container
// and we should export the mount function
export {mount};
It seems obj (from the response) could be used to manage state, for example:
const [receivedResults, setReceivedResults] = useState(false);
Then when the response is received:
setReceivedResults(obj === "clear");
and use the flag to allow the component to render itself (not sure of the exact syntax):
if (receivedResults) {
return <Redirect to="/result" />;
} else {
return null;
}
What is the best way to test this function
export function receivingItems() {
return (dispatch, getState) => {
axios.get('/api/items')
.then(function(response) {
dispatch(receivedItems(response.data));
});
};
}
this is currently what I have
describe('Items Action Creator', () => {
it('should create a receiving items function', () => {
expect(receivingItems()).to.be.a.function;
});
});
From Redux “Writing Tests” recipe:
For async action creators using Redux Thunk or other middleware, it’s best to completely mock the Redux store for tests. You can still use applyMiddleware() with a mock store, as shown below (you can find the following code in redux-mock-store). You can also use nock to mock the HTTP requests.
function fetchTodosRequest() {
return {
type: FETCH_TODOS_REQUEST
}
}
function fetchTodosSuccess(body) {
return {
type: FETCH_TODOS_SUCCESS,
body
}
}
function fetchTodosFailure(ex) {
return {
type: FETCH_TODOS_FAILURE,
ex
}
}
export function fetchTodos() {
return dispatch => {
dispatch(fetchTodosRequest())
return fetch('http://example.com/todos')
.then(res => res.json())
.then(json => dispatch(fetchTodosSuccess(json.body)))
.catch(ex => dispatch(fetchTodosFailure(ex)))
}
}
can be tested like:
import expect from 'expect'
import { applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import * as actions from '../../actions/counter'
import * as types from '../../constants/ActionTypes'
import nock from 'nock'
const middlewares = [ thunk ]
/**
* Creates a mock of Redux store with middleware.
*/
function mockStore(getState, expectedActions, done) {
if (!Array.isArray(expectedActions)) {
throw new Error('expectedActions should be an array of expected actions.')
}
if (typeof done !== 'undefined' && typeof done !== 'function') {
throw new Error('done should either be undefined or function.')
}
function mockStoreWithoutMiddleware() {
return {
getState() {
return typeof getState === 'function' ?
getState() :
getState
},
dispatch(action) {
const expectedAction = expectedActions.shift()
try {
expect(action).toEqual(expectedAction)
if (done && !expectedActions.length) {
done()
}
return action
} catch (e) {
done(e)
}
}
}
}
const mockStoreWithMiddleware = applyMiddleware(
...middlewares
)(mockStoreWithoutMiddleware)
return mockStoreWithMiddleware()
}
describe('async actions', () => {
afterEach(() => {
nock.cleanAll()
})
it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', (done) => {
nock('http://example.com/')
.get('/todos')
.reply(200, { todos: ['do something'] })
const expectedActions = [
{ type: types.FETCH_TODOS_REQUEST },
{ type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } }
]
const store = mockStore({ todos: [] }, expectedActions, done)
store.dispatch(actions.fetchTodos())
})
})
I would use a stub axios (for example by using mock-require) and write a test that actually calls receivingItems()(dispatch, getState) and makes sure dispatch is called with the correct data.
I solved this in a different way: injecting axios as a dependency of action. I prefer this approach over 'rewiring' dependencies.
So I used the same approach of testing redux-connected components. When I export actions I export two versions: one with (to be used for components) and one without (for testing) binding dependencies.
Here is how my actions.js file looks like:
import axios from 'axios'
export const loadDataRequest = () => {
return {
type: 'LOAD_DATA_REQUEST'
}
}
export const loadDataError = () => {
return {
type: 'LOAD_DATA_ERROR'
}
}
export const loadDataSuccess = (data) =>{
return {
type: 'LOAD_DATA_SUCCESS',
data
}
}
export const loadData = (axios) => {
return dispatch => {
dispatch(loadDataRequest())
axios
.get('http://httpbin.org/ip')
.then(({data})=> dispatch(loadDataSuccess(data)))
.catch(()=> dispatch(loadDataError()))
}
}
export default {
loadData: loadData.bind(null, axios)
}
Then testing with jest (actions.test.js):
import { loadData } from './actions'
describe('testing loadData', ()=>{
test('loadData with success', (done)=>{
const get = jest.fn()
const data = {
mydata: { test: 1 }
}
get.mockReturnValue(Promise.resolve({data}))
let callNumber = 0
const dispatch = jest.fn(params =>{
if (callNumber===0){
expect(params).toEqual({ type: 'LOAD_DATA_REQUEST' })
}
if (callNumber===1){
expect(params).toEqual({
type: 'LOAD_DATA_SUCCESS',
data: data
})
done()
}
callNumber++
})
const axiosMock = {
get
}
loadData(axiosMock)(dispatch)
})
})
When using the actions inside a component I import everything:
import Actions from './actions'
And to dispatch:
Actions.loadData() // this is the version with axios binded.