Reputation: 13
I'm trying to test a context with dummy child component.
Dummy component
function DefaultMockComponent() {
const values = useContext(CurrencyContext)
const valuesItems = Object.keys(values).map(value => {
if (typeof values[value] === 'function') {
return <div key={value} data-testid={value}>{value + '()'}</div>
} else {
return <div key={value} data-testid={value}>{values[value]}</div>
}
})
return(
<>
{valuesItems}
</>
)
}
Context unit test
// This test don't re render with the new data from fetch
it('should pass the correct values', async () => {
// This mock FAILS (see edit 2)
fetch
.mockResponse(req => {
if (/.*\/ticker\/.*/.test(req.url)) {
return new Promise(() => ({ body: JSON.stringify(tickerResponse) }))
} else if (/.*\/day-summary\/.*/.test(req.url)) {
return new Promise(() => ({ body: JSON.stringify(summaryResponse) }))
}
})
render(
<CurrencyProvider>
<DefaultMockComponent />
</CurrencyProvider>
)
await waitFor(() => expect(screen.getByTestId('currency').textContent).toBe("btc"))
await waitFor(() => expect(screen.getByTestId('volBRL').textContent).toBe("41198719.10154287"))
await waitFor(() => expect(screen.getByTestId('closing').textContent).toBe("326900.00666999"))
await waitFor(() => expect(screen.getByTestId('sell').textContent).toBe("360000.00000000"))
await waitFor(() => expect(screen.getByTestId('buy').textContent).toBe("359999.99006000"))
await waitFor(() => expect(screen.getByTestId('last').textContent).toBe("359999.99006000"))
await waitFor(() => expect(screen.getByTestId('vol').textContent).toBe("259.88030295"))
await waitFor(() => expect(screen.getByTestId('low').textContent).toBe("353684.14000000"))
await waitFor(() => expect(screen.getByTestId('high').textContent).toBe("380000.00000000"))
});
Here fetch is mocked. I'm reading the MockComponent information and comparing it with the response object information. My problem is that the MockComponent renders only with the default context state values, as you can see:
Rendered MockComponent
<body>
<div>
<div
data-testid="high"
>
0
</div>
<div
data-testid="low"
>
0
</div>
<div
data-testid="vol"
>
0
</div>
<div
data-testid="last"
>
0
</div>
<div
data-testid="buy"
>
0
</div>
<div
data-testid="sell"
>
0
</div>
<div
data-testid="closing"
>
0
</div>
<div
data-testid="volBRL"
>
0
</div>
<div
data-testid="currency"
>
btc
</div>
<div
data-testid="setCurrency"
>
setCurrency()
</div>
</div>
</body>
Which is strange since my Cypress E2E test works fine. How can I make the DefaultMockComponent to render the data returned from fetch?
EDIT 1
CurrencyProvider of CurrencyContext
export default function CurrencyProvider({children}) {
const [currency, setCurrency] = useState('btc')
const [high, setHigh] = useState('0')
const [low, setLow] = useState('0')
const [vol, setVol] = useState('0')
const [last, setLast] = useState('0')
const [buy, setBuy] = useState('0')
const [sell, setSell] = useState('0')
const [closing, setClosing] = useState('0')
const [volBRL, setVolBRL] = useState('0')
useEffect(update, [currency])
function update() {
const ticker = `https://www.mercadobitcoin.net/api/${currency}/ticker/`
fetch(ticker)
.then(response => response.json())
.then(data => {
setHigh(data.ticker.high)
setLow(data.ticker.low)
setVol(data.ticker.vol)
setLast(data.ticker.last)
setBuy(data.ticker.buy)
setSell(data.ticker.sell)
})
const date = new Date()
date.setDate(date.getDate() - 1)
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const summary = `https://www.mercadobitcoin.net/api/${currency}/day-summary/${year}/${month}/${day}/`
fetch(summary)
.then(response => response.json())
.then(data => {
setClosing(data.closing)
setVolBRL(data.volume)
})
}
return(
<CurrencyContext.Provider value={{
high,
low,
vol,
last,
buy,
sell,
closing,
volBRL,
currency,
setCurrency
}}>
{children}
</CurrencyContext.Provider>
)
}
EDIT 2
Thanks to lissettdm. Now the fetch function isn't failing, but the values still not being updated plus I'm getting "An update to CurrencyProvider inside a test was not wrapped in act(...)" warning. Following the mock code that works:
Mock without jest-fetch-mock (as lissettdm answer)
const fetchSpy = jest.spyOn(window, "fetch").mockImplementation((req) =>
Promise.resolve({
json: () => {
if (/.*\/ticker\/.*/.test(req)) {
return tickerResponse
} else if (/.*\/day-summary\/.*/.test(req)) {
return summaryResponse
}
},
})
);
Mock with jest-fetch-mock
fetch
.mockResponse(req => {
if (/.*\/ticker\/.*/.test(req.url)) {
return Promise.resolve({ body: JSON.stringify(tickerResponse) })
} else if (/.*\/day-summary\/.*/.test(req.url)) {
return Promise.resolve({ body: JSON.stringify(summaryResponse) })
}
})
Upvotes: 1
Views: 763
Reputation: 13080
You see only the default context values because the fetch function is failing. The first then
expect that the response object contains json
function and that part is missing.
You need to add json
function to your mock implementation :
const fetchSpy = jest.spyOn(window, "fetch").mockImplementationOnce((req) =>
Promise.resolve({
json: () => {
if (/.*\/ticker\/.*/.test(req.url)) {
return new Promise(() => ({ body: JSON.stringify(tickerResponse) }))
} else if (/.*\/day-summary\/.*/.test(req.url)) {
return new Promise(() => ({ body: JSON.stringify(summaryResponse) }))
}
},
}));
//--> check if fetch was called
expect(fetchSpy).toHaveBeenCalled();
About this warning:
An update to CurrencyProvider inside a test was not wrapped in act(...)" warning
Probably, your testing is ending without the state be flushed. I think you should use findBy
methods which are a combination of getBy
queries and waitFor
const currency = await screen.getByTestId('currency');
expect(currency.textContent).toBe("btc");
Upvotes: 1