How to write unit test case for async method in react? - react-redux

I am trying to write a unit test case for an async function which having for loop inside and in each iteration it calls Axios "get" method.
I am new to unit test cases, and I know how to write it for simple async function, but for the complicated case, I need some help. Thanks in advance.
export const someMethod = ({ collections }) => {
return dispatch => {
if (collections.length > 0) {
for (let index = 0; index < collections.length; index++) {
const collection = collections[index];
const { libraryUrl, bookUrl } = collection;
const containerId = Math.random().toString();
dispatch(getBook({ boolURL: bookUrl, libraryURL: libraryUrl, libraryId }));
dispatch({
type: ASSESSMENT.BOOK.ADD,
payload: { boolURL: bookUrl, libraryId }
});
}
}
};
};

Here is the solution:
actionCreators.ts:
export const ASSESSMENT = {
BOOK: {
ADD: 'ADD',
GET: 'GET'
}
};
export const getBook = data => ({ type: ASSESSMENT.BOOK.GET, payload: { data } });
export const someMethod = ({ collections }) => {
return dispatch => {
if (collections.length > 0) {
for (let index = 0; index < collections.length; index++) {
const collection = collections[index];
const { libraryUrl, bookUrl, libraryId } = collection;
const containerId = Math.random().toString();
dispatch(getBook({ boolURL: bookUrl, libraryURL: libraryUrl, libraryId }));
dispatch({
type: ASSESSMENT.BOOK.ADD,
payload: { boolURL: bookUrl, libraryId }
});
}
}
};
};
actionCreators.spec.ts:
import { someMethod, ASSESSMENT } from './actionCreators';
import createMockStore from 'redux-mock-store';
import thunk, { ThunkDispatch } from 'redux-thunk';
import { AnyAction } from 'redux';
const middlewares = [thunk];
const mockStore = createMockStore<any, ThunkDispatch<any, any, AnyAction>>(middlewares);
describe('someMethod', () => {
it('t1', () => {
const intialState = {};
const store = mockStore(intialState);
const collections = [
{ libraryUrl: 'aa', bookUrl: 'a', libraryId: '1' },
{ libraryUrl: 'bb', bookUrl: 'b', libraryId: '2' }
];
const expectedActions = [
{
type: ASSESSMENT.BOOK.GET,
payload: {
data: {
boolURL: collections[0].bookUrl,
libraryURL: collections[0].libraryUrl,
libraryId: collections[0].libraryId
}
}
},
{ type: ASSESSMENT.BOOK.ADD, payload: { boolURL: collections[0].bookUrl, libraryId: collections[0].libraryId } },
{
type: ASSESSMENT.BOOK.GET,
payload: {
data: {
boolURL: collections[1].bookUrl,
libraryURL: collections[1].libraryUrl,
libraryId: collections[1].libraryId
}
}
},
{ type: ASSESSMENT.BOOK.ADD, payload: { boolURL: collections[1].bookUrl, libraryId: collections[1].libraryId } }
];
store.dispatch(someMethod({ collections }));
expect(store.getActions()).toEqual(expectedActions);
});
});
Unit test result with coverage report:
PASS src/stackoverflow/58012116/actionCreators.spec.ts
someMethod
✓ t1 (6ms)
-------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 50 | 100 | 100 | |
actionCreators.ts | 100 | 50 | 100 | 100 | 12 |
-------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 5.523s
Here is the completed demo:https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58012116

Related

Warning: Expected server HTML to contain a matching <div> in <div> when use localStorage as params

I think this warning appear when i work with localStorage, some answers in another page is use useEffect(), but I don't know how to use query or mutation in useEffect().How can I fix it?
export const productListForBill = () : GetProductForBill[] =>{
const returnEmtpyArray : GetProductForBill[] = []
if(typeof window !== "undefined"){
if(localStorage.getItem("products"))
{
const tempProduct = JSON.parse(localStorage.getItem("products") || "")
if(Array.isArray(tempProduct)){
return tempProduct
}
}
}
return returnEmtpyArray
}
const Cart = () => {
const { data } = useGetSomeProductQuery({
variables: { productList: productListForBill() },
});
return (
<>
<Navbar />
{data?.getSomeProduct ? (
data.getSomeProduct.map((product) => (
<div key={`${product.name}-${product.type}`}>
name: {product.name} --|-- type: {product.type} --|-- amount :{" "}
{product.amount} --|-- unitPrice : {product.unitPrice} --|-- total:{" "}
{product.totalPrice}
</div>
))
) : (
<div>Nothing in here</div>
)}
</>
);
};
export const getStaticProps: GetStaticProps = async () => {
const apolloClient = initializeApollo();
await apolloClient.query({
query: GetSomeProductDocument,
variables: { productList: productListForBill() },
});
return addApolloState(apolloClient, {
props: {},
});
};
I have to type something for text checker of Stackoverflow, have a nice day!
code of useGetSomeProductQuery, i'm working with graphql and use codegen to generate it at client
#Query((_return) => [ProductOfBill], { nullable: true })
async getSomeProduct(
#Arg("productList", (_type) => [GetProductForBill])
productList: GetProductForBill[]
): Promise<ProductOfBill[] | null | undefined> {
try {
const newList : ProductOfBill[] = await Promise.all(productList.map(async (product) => {
const price = await Price.findOne({
where: {
type: product.type,
product: product.productId,
}
});
const newProductOfBill = ProductOfBill.create({
name:product.name,
amount:product.amount,
type:product.type,
unitPrice:price?.price
})
return newProductOfBill
}))
.then(list => {
console.log(list)
return list
})
.catch(_ => {
const resultList : ProductOfBill[] = []
return resultList
})
return newList;
} catch (error) {
console.log(error);
return undefined;
}
}

Pagination Apollo Client 3 - Cache merges but does not render new results when paginating

I'd love to implement nested pagination within my application. I have been reading the docs and looking at several other examples but I just can't get this to work - any help is appreciated! Thanks!
React component:
I am clicking the button to run the fetchMore function provided by the useQuery hook (apollo). The network request is going through and the new products are merged into the cache... but no new products render on the page.
export const FilterableKit = () => {
const selectedKitId = useReactiveVar(selectedKitIdVar);
const [
getKitProducts,
{ data: getKitProductsData, loading: getKitProductsLoading, fetchMore },
] = useGetKitProductsLazyQuery();
useEffect(() => {
if (selectedKitId) {
getKitProducts({
variables: {
getKitsInput: {
_id: {
string: selectedKitId,
filterBy: "OBJECTID" as StringFilterByEnum,
},
},
getProductsInput: {
config: {
pagination: {
reverse: true,
limit: 3,
},
},
},
},
});
}
}, [getKitProducts, selectedKitId]);
const kitProducts = getKitProductsData?.getKits.data?.find(
(kit) => kit?._id === selectedKitId
)?.products.data;
const handleLoadMore = () => {
if (kitProducts && kitProducts?.length > 0) {
const remaining =
getKitProductsData?.getKits.data[0]?.products.stats?.remaining;
if (remaining && remaining > 0) {
const cursor =
kitProducts[kitProducts.length - 1] &&
kitProducts[kitProducts.length - 1]?.createdAt;
fetchMore({
variables: {
getProductsInput: {
config: {
pagination: {
reverse: true,
createdAt: cursor,
},
},
},
},
});
}
}
};
return (
<CContainer>
<KitItemCards products={kitProducts} loading={getKitProductsLoading} />
<CContainer className="d-flex justify-content-center my-3">
<CButton color="primary" className="w-100" onClick={handleLoadMore}>
Load More
</CButton>
</CContainer>
</CContainer>
);
};
Type Policies: I define the "Kit" typePolicy to merge products into the correct field.
export const cache: InMemoryCache = new InMemoryCache({
typePolicies: {
Kit: {
fields: {
products: {
keyArgs: false,
merge(existing = [] as Product[], incoming: GetProductsResponse) {
if (!incoming) return existing;
if (!existing) return incoming;
const { data: products, ...rest } = incoming;
let result: any = rest;
result = [...existing, ...(products ?? [])];
return result;
},
},
},
},
});
Thanks for any pointers in the right direction! Let me know if there is something else you'd like to see.

RxJs with Jest case fails but the `tap` output shows correctly

Following test case, will return the data correctly but JEST show as failed. The test is written using TestScheduler
Jest Result
expect(received).toEqual(expected) // deep equality
- Expected
+ Received
- Array [
- Object {
- "frame": 3,
- "notification": Notification {
- "error": undefined,
- "hasValue": true,
- "kind": "N",
- "value": Object {
- "type": "INITIALIZED",
- },
- },
- },
- ]
+ Array []
Code
import { ofType } from 'redux-observable';
import { mergeMap, map, tap } from 'rxjs/operators';
import { of, from } from 'rxjs';
import { TestScheduler } from 'rxjs/testing';
describe('routechange epic', () => {
const testScheduler = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
it('check apollo', () => {
const dependencies = {
apolloClient: {
mutate: ({ mutation, variables }: { mutation: any, variables: any }) =>
Promise.resolve({
data: { param: 'testA' }
})
},
};
const initializeOrg = (action$, state$, { apolloClient }) =>
action$
.pipe(
ofType('START'),
tap(act => console.log('---AAA', act)),
mergeMap(action =>
from(
apolloClient.mutate({
mutation: `something`,
variables: {
orgId: (action as any).params || ''
}
})
)
.pipe(
tap(x => console.log('----x', x)),
map(response => ({
type: 'INITIALIZED',
response,
}))
)
)
);
testScheduler.run(({ hot, cold, expectObservable }) => {
const action$ = hot('-a', {
a: { type: 'START', params: 'SomethingA' }
});
const state$ = null;
const output$ = initializeOrg(action$, state$, dependencies);
expectObservable(output$).toBe('---a', {
a: {
type: 'INITIALIZED'
}
})
});
});
});
We cannot use Promise.resolve as someone commented it.
It worked now with cold observable like below.
import { ofType } from 'redux-observable';
import { mergeMap, map, tap, toArray, take } from 'rxjs/operators';
import { of, from } from 'rxjs';
import { TestScheduler } from 'rxjs/testing';
describe('routechange epic', () => {
it('check apollo', async () => {
const testScheduler = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
const initializeOrg = (action$, state$, { apolloClient }) =>
action$
.pipe(
ofType('START'),
tap(act => console.log('---AAA', act)),
mergeMap(action =>
from(
apolloClient.mutate({
mutation: `something`,
variables: {
orgId: (action as any).params || ''
}
})
)
.pipe(
tap(x => console.log('----x', x)),
map(response => ({
type: 'INITIALIZED',
response,
}))
)
)
);
testScheduler.run(({ hot, cold, expectObservable }) => {
const action$ = hot('-a', {
a: { type: 'START', params: 'SomethingA' }
});
const state$ = null;
const dependencies = {
apolloClient: {
mutate: ({ mutation, variables }: { mutation: any, variables: any }) =>
cold('--a|', {
a: { data: { param: 'testA' } }
})
},
};
const output$ = initializeOrg(action$, state$, dependencies);
expectObservable(output$).toBe('---a', {
a: {
type: 'INITIALIZED',
response: {
data: { param: 'testA' }
}
}
})
});
});
});

Sort() not working

I'm having an issue with the sort() in ranking data from coinmarketcap api. With an ajax api call, sort works in returning an array with the right ranking. With an axios api call, seen below, it doesn't.
Here is my code using axios and vue.js:
let coinMarket = 'https://api.coinmarketcap.com/v2/ticker/?limit=10'
let updateInterval = 60 * 1000;
let newApp = new Vue({
el: '#coinApp',
data: {
// data within an array
results: []
},
methods: {
getCoins: function() {
axios
.get(coinMarket)
.then((resp) => {
this.results = formatCoins(resp);
});
},
getColor: (num) => {
return num > 0 ? "color:green;" : "color:red;";
},
},
created: function() {
this.getCoins();
}
})
setInterval(() => {
newApp.getCoins();
},
updateInterval
);
function formatCoins(res) {
var coinDataArray = []
Object.keys(res.data).forEach(function(key) {
coinDataArray.push(res.data[key])
})
coinDataArray.sort(function(a,b) {
return a.rank > b.rank
})
console.log(coinDataArray)
}
Where am I going wrong?
If you look into the data responded by https://api.coinmarketcap.com/v2/ticker/?limit=10, you will find the data you need is under res.data.data, not res.data.
So within the function=formatCoins, replace res.data with res.data.data, then works.
Vue.config.productionTip = false
let coinMarket = 'https://api.coinmarketcap.com/v2/ticker/?limit=10'
let updateInterval = 60 * 1000;
function formatCoins(res) {
var coinDataArray = []
Object.keys(res.data.data).forEach(function(key) {
coinDataArray.push(res.data.data[key])
})
coinDataArray.sort(function(a,b) {
return a.rank - b.rank
})
return coinDataArray
}
let newApp = new Vue({
el: '#coinApp',
data: {
// data within an array
results: []
},
methods: {
getCoins: function() {
axios
.get(coinMarket)
.then((resp) => {
this.results = formatCoins(resp);
});
},
getColor: (num) => {
return num > 0 ? "color:green;" : "color:red;";
},
},
created: function() {
this.getCoins();
}
})
setInterval(() => {
newApp.getCoins();
},
updateInterval
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.js"></script>
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="coinApp">
<div v-for="(record, index) in results" :key="index">
{{index}} - {{record.name}}: {{record.rank}}
</div>
</div>

How can I select a part of a array of objects in a GraphQL query?

My resolver get
{ adminMsg:
[
{active: “y”, text1: “blah1" } ,
{active: “n”, text1: “blah2" }
] };
My query:
{
adminWarn {
adminMsg {
active, text1
}
}
}
I want only array-elements with condition: active = 'y'
I find in GQL Dokumentation no way to write this condition im my query.
Is there any solution in GQL?
Use of resolve args can solve the problem:
const adminWarnList = new GraphQLObjectType({
name: 'adminWarnReportList',
fields: () => ({
adminMsg: {
type: new GraphQLList(adminWarnFields),
},
}),
});
const adminWarn = {
type: adminWarnList,
args: {
active: { type: GraphQLString },
},
resolve: (parent, args, context) => {
...
let reportdata = context.loadData();
if (args.active == 'y') {
let filteredItems = reportdata.filter(function(item) {
return item.active != null && item.active != 'y';
});
reportdata = filteredItems;
}
return { adminMsg: reportdata };
},
};

Resources