diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 1b7d35a197..2a290802d3 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -38,6 +38,7 @@ "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", "@types/react": "^16.9", + "msw": "^0.21.3", "react": "^16.12.0", "react-dom": "^16.12.0", "react-router": "6.0.0-beta.0", diff --git a/packages/test-utils/src/testUtils/msw/index.ts b/packages/test-utils/src/testUtils/msw/index.ts index 5bae182cb1..0deaac0986 100644 --- a/packages/test-utils/src/testUtils/msw/index.ts +++ b/packages/test-utils/src/testUtils/msw/index.ts @@ -13,8 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export const msw = { - setupDefaultHandlers: (worker: any) => { + setupDefaultHandlers: (worker: { + listen: (t: any) => void; + close: () => void; + resetHandlers: () => void; + }) => { beforeAll(() => worker.listen({ onUnhandledRequest: 'error' })); afterAll(() => worker.close()); afterEach(() => worker.resetHandlers()); diff --git a/plugins/lighthouse/src/components/AuditList/index.test.tsx b/plugins/lighthouse/src/components/AuditList/index.test.tsx index bc75cc5d98..5fdc4781ce 100644 --- a/plugins/lighthouse/src/components/AuditList/index.test.tsx +++ b/plugins/lighthouse/src/components/AuditList/index.test.tsx @@ -26,7 +26,7 @@ jest.mock('react-router-dom', () => { import React from 'react'; import { render, fireEvent } from '@testing-library/react'; import { ApiRegistry, ApiProvider } from '@backstage/core'; -import { wrapInTestApp } from '@backstage/test-utils'; +import { wrapInTestApp, msw } from '@backstage/test-utils'; import { lighthouseApiRef, @@ -39,10 +39,15 @@ import * as data from '../../__fixtures__/website-list-response.json'; const { useNavigate } = jest.requireMock('react-router-dom'); const websiteListResponse = data as WebsiteListResponse; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; describe('AuditList', () => { let apis: ApiRegistry; + const server = setupServer(); + msw.setupDefaultHandlers(server); + beforeEach(() => { apis = ApiRegistry.from([ [lighthouseApiRef, new LighthouseRestApi('http://lighthouse')], @@ -50,6 +55,7 @@ describe('AuditList', () => { }); it('should render the table', async () => { + server.use(rest.get('*', (_req, res, ctx) => res(ctx.json(data)))); const rendered = render( wrapInTestApp( @@ -72,24 +78,8 @@ describe('AuditList', () => { const button = await rendered.findByText('Create Audit'); expect(button).toBeInTheDocument(); }); - /* need to rewrite these tests */ - /* - describe('pagination', () => { - it('requests the correct limit and offset from the api based on the query', () => { - render( - wrapInTestApp( - - - , - { routeEntries: ['?page=2'] }, - ), - ); - expect(mockFetch).toHaveBeenLastCalledWith( - 'http://lighthouse/v1/websites?limit=10&offset=10', - undefined, - ); - }); + describe('pagination', () => { describe('when only one page is needed', () => { it('hides pagination elements', () => { const rendered = render( @@ -109,7 +99,8 @@ describe('AuditList', () => { response.limit = 5; response.offset = 5; response.total = 7; - mockFetch.mockResponseOnce(JSON.stringify(response)); + server.use(rest.get('*', (_req, res, ctx) => res(ctx.json(response)))); + server.use(rest.post('*', (_req, res, ctx) => res(ctx.json(response)))); }); it('shows pagination elements', async () => { @@ -144,7 +135,7 @@ describe('AuditList', () => { describe('when waiting on the request', () => { it('should render the loader', async () => { - mockFetch.mockResponseOnce(() => new Promise(() => {})); + server.use(rest.get('*', (_req, res, ctx) => res(ctx.delay(20000)))); const rendered = render( wrapInTestApp( @@ -159,7 +150,11 @@ describe('AuditList', () => { describe('when the audits fail', () => { it('should render an error', async () => { - mockFetch.mockRejectOnce(new Error('failed to fetch')); + server.use( + rest.get('*', (_req, res, ctx) => + res(ctx.status(500, 'something broke')), + ), + ); const rendered = render( wrapInTestApp( @@ -171,5 +166,4 @@ describe('AuditList', () => { expect(element).toBeInTheDocument(); }); }); - */ }); diff --git a/plugins/lighthouse/src/components/AuditView/index.test.tsx b/plugins/lighthouse/src/components/AuditView/index.test.tsx index ca292a133d..8bfa8ce073 100644 --- a/plugins/lighthouse/src/components/AuditView/index.test.tsx +++ b/plugins/lighthouse/src/components/AuditView/index.test.tsx @@ -27,17 +27,18 @@ jest.mock('react-router-dom', () => { }); import React from 'react'; -import mockFetch from 'jest-fetch-mock'; import { render, fireEvent } from '@testing-library/react'; -import { wrapInTestApp } from '@backstage/test-utils'; +import { wrapInTestApp, msw } from '@backstage/test-utils'; import { ApiRegistry, ApiProvider } from '@backstage/core'; - import AuditView from '.'; import { lighthouseApiRef, LighthouseRestApi, Audit, Website } from '../../api'; import { formatTime } from '../../utils'; import * as data from '../../__fixtures__/website-response.json'; import { act } from 'react-dom/test-utils'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; + const { useParams }: { useParams: jest.Mock } = jest.requireMock( 'react-router-dom', ); @@ -48,8 +49,16 @@ describe('AuditView', () => { let apis: ApiRegistry; let id: string; + const server = setupServer(); + msw.setupDefaultHandlers(server); + beforeEach(() => { - mockFetch.mockResponse(JSON.stringify(websiteResponse)); + server.use( + rest.get('https://lighthouse/*', async (_req, res, ctx) => + res(ctx.json(websiteResponse)), + ), + ); + apis = ApiRegistry.from([ [lighthouseApiRef, new LighthouseRestApi('https://lighthouse')], ]); @@ -74,27 +83,6 @@ describe('AuditView', () => { expect(iframe).toHaveAttribute('src', `https://lighthouse/v1/audits/${id}`); }); - it('renders a button to click to create a new audit for this website', async () => { - const rendered = render( - wrapInTestApp( - - - , - ), - ); - - const button = await rendered.findByText('Create New Audit'); - expect(button).toBeInTheDocument(); - - act(() => { - fireEvent.click(button); - }); - - expect(useNavigate()).toHaveBeenCalledWith( - `../../create-audit?url=${encodeURIComponent('https://spotify.com')}`, - ); - }); - describe('sidebar', () => { it('renders a list of all audits for the website', async () => { const rendered = render( @@ -164,7 +152,7 @@ describe('AuditView', () => { describe('when the request for the website by id is pending', () => { it('shows the loading', async () => { - mockFetch.mockImplementationOnce(() => new Promise(() => {})); + server.use(rest.get('*', (_req, res, ctx) => res(ctx.delay(20000)))); const rendered = render( wrapInTestApp( @@ -178,7 +166,11 @@ describe('AuditView', () => { describe('when the request for the website by id fails', () => { it('shows an error', async () => { - mockFetch.mockRejectOnce(new Error('failed to fetch')); + server.use( + rest.get('*', (_req, res, ctx) => + res(ctx.status(500), ctx.body('failed to fetch')), + ), + ); const rendered = render( wrapInTestApp( @@ -186,7 +178,7 @@ describe('AuditView', () => { , ), ); - expect(await rendered.findByText('failed to fetch')).toBeInTheDocument(); + expect(await rendered.findByText(/failed to fetch/)).toBeInTheDocument(); }); }); diff --git a/plugins/lighthouse/src/components/CreateAudit/index.test.tsx b/plugins/lighthouse/src/components/CreateAudit/index.test.tsx index b2348bf5ff..96829926b5 100644 --- a/plugins/lighthouse/src/components/CreateAudit/index.test.tsx +++ b/plugins/lighthouse/src/components/CreateAudit/index.test.tsx @@ -24,20 +24,22 @@ jest.mock('react-router-dom', () => { }); import React from 'react'; -import mockFetch from 'jest-fetch-mock'; -import { wait, render, fireEvent } from '@testing-library/react'; +import { waitFor, render, fireEvent } from '@testing-library/react'; import { ApiRegistry, ApiProvider, ErrorApi, errorApiRef, } from '@backstage/core'; -import { wrapInTestApp } from '@backstage/test-utils'; +import { wrapInTestApp, msw } from '@backstage/test-utils'; import { lighthouseApiRef, LighthouseRestApi, Audit } from '../../api'; import CreateAudit from '.'; import * as data from '../../__fixtures__/create-audit-response.json'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; + const { useNavigate }: { useNavigate: jest.Mock } = jest.requireMock( 'react-router-dom', ); @@ -47,6 +49,8 @@ const createAuditResponse = data as Audit; describe('CreateAudit', () => { let apis: ApiRegistry; let errorApi: ErrorApi; + const server = setupServer(); + msw.setupDefaultHandlers(server); beforeEach(() => { errorApi = { post: jest.fn(), error$: jest.fn() }; @@ -88,7 +92,7 @@ describe('CreateAudit', () => { describe('when waiting on the request', () => { it('disables the form fields', () => { - mockFetch.mockResponseOnce(() => new Promise(() => {})); + server.use(rest.get('*', (_req, res, ctx) => res(ctx.delay(20000)))); const rendered = render( wrapInTestApp( @@ -111,7 +115,11 @@ describe('CreateAudit', () => { describe('when the audit is successfully created', () => { it('triggers a location change to the table', async () => { useNavigate.mockClear(); - mockFetch.mockResponseOnce(JSON.stringify(createAuditResponse)); + server.use( + rest.post('http://lighthouse/v1/audits', (_req, res, ctx) => + res(ctx.json(createAuditResponse)), + ), + ); const rendered = render( wrapInTestApp( @@ -126,14 +134,7 @@ describe('CreateAudit', () => { }); fireEvent.click(rendered.getByText(/Create Audit/)); - expect(mockFetch).toHaveBeenCalledWith( - 'http://lighthouse/v1/audits', - expect.objectContaining({ - method: 'POST', - }), - ); - - await wait(() => expect(rendered.getByLabelText(/URL/)).toBeEnabled()); + await waitFor(() => expect(rendered.getByLabelText(/URL/)).toBeEnabled()); expect(useNavigate()).toHaveBeenCalledWith('..'); }); @@ -141,9 +142,11 @@ describe('CreateAudit', () => { describe('when the audits fail', () => { it('should render an error', async () => { - (errorApi.post as jest.Mock).mockClear(); - mockFetch.mockRejectOnce(new Error('failed to post')); - + server.use( + rest.post('http://lighthouse/v1/audits', (_req, res, ctx) => + res(ctx.status(500, 'failed to post')), + ), + ); const rendered = render( wrapInTestApp( @@ -157,8 +160,7 @@ describe('CreateAudit', () => { }); fireEvent.click(rendered.getByText(/Create Audit/)); - await wait(() => expect(rendered.getByLabelText(/URL/)).toBeEnabled()); - await new Promise(r => setTimeout(r, 0)); + await waitFor(() => expect(rendered.getByLabelText(/URL/)).toBeEnabled()); expect(errorApi.post).toHaveBeenCalledWith(expect.any(Error)); }); diff --git a/plugins/lighthouse/src/setupTests.ts b/plugins/lighthouse/src/setupTests.ts index 825bcd4115..aea2220869 100644 --- a/plugins/lighthouse/src/setupTests.ts +++ b/plugins/lighthouse/src/setupTests.ts @@ -15,3 +15,4 @@ */ import '@testing-library/jest-dom'; +import 'cross-fetch/polyfill'; diff --git a/yarn.lock b/yarn.lock index 6e48747b66..9f1bfaa3b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9061,7 +9061,7 @@ cross-env@^7.0.0: dependencies: cross-spawn "^7.0.1" -cross-fetch@3.0.6, cross-fetch@^3.0.4, cross-fetch@^3.0.5, cross-fetch@^3.0.6: +cross-fetch@3.0.6, cross-fetch@^3.0.5, cross-fetch@^3.0.6: version "3.0.6" resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c" integrity sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ== @@ -14229,14 +14229,6 @@ jest-esm-transformer@^1.0.0: "@babel/core" "^7.4.4" "@babel/plugin-transform-modules-commonjs" "^7.4.4" -jest-fetch-mock@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b" - integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw== - dependencies: - cross-fetch "^3.0.4" - promise-polyfill "^8.1.3" - jest-get-type@^25.2.6: version "25.2.6" resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877" @@ -16399,6 +16391,24 @@ msw@^0.21.2: statuses "^2.0.0" yargs "^16.0.3" +msw@^0.21.3: + version "0.21.3" + resolved "https://registry.npmjs.org/msw/-/msw-0.21.3.tgz#d073842f9570a08f4041806a2c7303a9b8494602" + integrity sha512-voPc/EJsjarvi454vSEuozZQQqLG4AUHT6qQL5Ah47lq7sGCpc7icByeUlfvEj5+MvaugN0c7JwXyCa2rxu8cA== + dependencies: + "@open-draft/until" "^1.0.3" + "@types/cookie" "^0.4.0" + chalk "^4.1.0" + chokidar "^3.4.2" + cookie "^0.4.1" + graphql "^15.3.0" + headers-utils "^1.2.0" + node-fetch "^2.6.1" + node-match-path "^0.4.4" + node-request-interceptor "^0.5.1" + statuses "^2.0.0" + yargs "^16.0.3" + multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" @@ -18692,11 +18702,6 @@ promise-inflight@^1.0.1: resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= -promise-polyfill@^8.1.3: - version "8.1.3" - resolved "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz#8c99b3cf53f3a91c68226ffde7bde81d7f904116" - integrity sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g== - promise-retry@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz#6739e968e3051da20ce6497fb2b50f6911df3d6d"