Skip to content

Commit 241e7e6

Browse files
authored
feat: polyfill fetch and example (#167)
Stubbing `fetch` requests by polyfilling them and then using Cypress network control
1 parent 575cdaf commit 241e7e6

File tree

9 files changed

+108
-1
lines changed

9 files changed

+108
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ Test | Description
250250
[styles](cypress/components/styles) | Shows inline, CSS and external stylesheet styles in spec
251251
[tutorial](cypress/components/tutorial) | A few components and tests from Svelte tutorial
252252
[mocking-fetch](cypress/components/mocking-fetch) | Mocking `window.fetch` before mounting the component
253+
[mocking-network](cypress/components/mocking-network) | Polyfills `window.fetch` [automatically](https://www.cypress.io/blog/2020/06/29/experimental-fetch-polyfill/) and tests the component
253254
<!-- prettier-ignore-end -->
254255

255256
## Related tools
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# mocking network example
2+
3+
In [Users.svelte](Users.svelte) the application is requesting a list of resources using `fetch`. We polyfill the `window.fetch` to fall back to `XMLHttpRequest` that Cypress can observe and stub using [experimentalFetchPolyfill](https://www.cypress.io/blog/2020/06/29/experimental-fetch-polyfill/)
4+
5+
![Loading test](images/loading-test.png)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/// <reference types="cypress" />
2+
import Users from './Users.svelte'
3+
import { mount } from 'cypress-svelte-unit-test'
4+
5+
describe('Mocking network', () => {
6+
let mockUsers
7+
8+
before(() => {
9+
// load the mock user list once from cypress/fixtures/users.json
10+
cy.fixture('users').then((list) => {
11+
expect(list).to.have.length(2)
12+
mockUsers = list
13+
})
14+
})
15+
16+
it('shows 3 users without mocking', () => {
17+
mount(Users)
18+
cy.get('.user').should('have.length', 3)
19+
})
20+
21+
it('stubs network request', () => {
22+
cy.server()
23+
cy.route('/users?_limit=3', 'fixture:users').as('users')
24+
mount(Users)
25+
26+
cy.wait('@users')
27+
28+
cy.get('.user').should('have.length', mockUsers.length)
29+
cy.get('.user')
30+
.first()
31+
.should('have.text', `${mockUsers[0].id} - ${mockUsers[0].name}`)
32+
})
33+
34+
it('shows loading element', () => {
35+
cy.server()
36+
cy.route({
37+
url: '/users?_limit=3',
38+
response: 'fixture:users',
39+
delay: 1000,
40+
})
41+
mount(Users)
42+
cy.get('.loading').should('be.visible')
43+
// and once the list is loaded, the loading indicator goes away
44+
cy.get('.loading').should('not.exist')
45+
cy.get('.user').should('not.be.empty')
46+
})
47+
})
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script>
2+
import { onMount } from "svelte";
3+
4+
let users = [];
5+
6+
onMount(async () => {
7+
const res = await fetch('https://jsonplaceholder.cypress.io/users?_limit=3');
8+
users = await res.json();
9+
});
10+
</script>
11+
12+
<div>
13+
<h1>Users List</h1>
14+
{#each users as user}
15+
<ul>
16+
<li class="user">
17+
<span>{user.id} - {user.name}</span>
18+
</li>
19+
</ul>
20+
{:else}
21+
<!-- this block renders when users.length === 0 -->
22+
<p class="loading">loading...</p>
23+
{/each}
24+
</div>
313 KB
Loading

lib/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
checkMountModeEnabled,
33
cleanupStyles,
44
injectStylesBeforeElement,
5+
polyfillFetchIfNeeded,
56
} from './utils'
67

78
const rootId = 'cypress-root'
@@ -127,4 +128,8 @@ export function mount(
127128
})
128129
}
129130

131+
beforeEach(() => {
132+
polyfillFetchIfNeeded()
133+
})
134+
130135
export default mount

lib/utils.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { StyleOptions } from '.'
2+
import unfetch from 'unfetch'
23

34
export function checkMountModeEnabled() {
45
// @ts-ignore
@@ -141,3 +142,21 @@ export const injectStylesBeforeElement = (
141142

142143
return insertLocalCssFiles(cssFiles, document, el, options.log)
143144
}
145+
146+
/**
147+
* Replaces window.fetch with a polyfill based on XMLHttpRequest
148+
* that Cypress can spy on and stub
149+
* @see https://www.cypress.io/blog/2020/06/29/experimental-fetch-polyfill/
150+
*/
151+
export function polyfillFetchIfNeeded() {
152+
// @ts-ignore
153+
if (Cypress.config('experimentalFetchPolyfill')) {
154+
// @ts-ignore
155+
if (!cy.state('fetchPolyfilled')) {
156+
delete window.fetch
157+
window.fetch = unfetch
158+
// @ts-ignore
159+
cy.state('fetchPolyfilled', true)
160+
}
161+
}
162+
}

package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@
4444
"semantic-release": "semantic-release"
4545
},
4646
"dependencies": {
47-
"@bahmutov/cy-rollup": "2.0.0"
47+
"@bahmutov/cy-rollup": "2.0.0",
48+
"unfetch": "4.1.0"
4849
},
4950
"devDependencies": {
5051
"@cypress/code-coverage": "3.8.1",

0 commit comments

Comments
 (0)