add and use MockFetchApi

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2022-01-06 19:46:33 +01:00
parent aecfe4f403
commit 6bf7826258
23 changed files with 284 additions and 106 deletions
+1
View File
@@ -15,3 +15,4 @@
*/
import '@testing-library/jest-dom';
import 'cross-fetch/polyfill';
@@ -0,0 +1,78 @@
/*
* Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { setupRequestMockHandlers } from '../../msw';
import { MockFetchApi } from './MockFetchApi';
describe('MockFetchApi', () => {
const worker = setupServer();
setupRequestMockHandlers(worker);
it('works with default constructor', async () => {
worker.use(
rest.get('http://example.com/data.json', (_, res, ctx) =>
res(ctx.status(200), ctx.json({ a: 'foo' })),
),
);
const m = new MockFetchApi();
const response = await m.fetch('http://example.com/data.json');
await expect(response.json()).resolves.toEqual({ a: 'foo' });
});
it('works with a mock implementation', async () => {
const inner = jest.fn();
const m = new MockFetchApi(inner);
await m.fetch('http://example.com/data.json');
expect(inner).lastCalledWith('http://example.com/data.json');
});
describe('setAuthorization', () => {
it('works with the default', async () => {
const inner = jest.fn();
const m = new MockFetchApi(inner).setAuthorization();
await m.fetch('http://example.com/data.json');
expect(inner.mock.calls[0][0].headers.get('authorization')).toBe(
'Bearer mocked',
);
});
it('works with a static token', async () => {
const inner = jest.fn();
const m = new MockFetchApi(inner).setAuthorization({ token: 'hello' });
await m.fetch('http://example.com/data.json');
expect(inner.mock.calls[0][0].headers.get('authorization')).toBe(
'Bearer hello',
);
});
it('works with an identity api', async () => {
const inner = jest.fn();
const m = new MockFetchApi(inner).setAuthorization({
identityApi: {
async getCredentials() {
return { token: 'hello2' };
},
},
});
await m.fetch('http://example.com/data.json');
expect(inner.mock.calls[0][0].headers.get('authorization')).toBe(
'Bearer hello2',
);
});
});
});
@@ -0,0 +1,84 @@
/*
* Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FetchApi, IdentityApi } from '@backstage/core-plugin-api';
import crossFetch, { Request } from 'cross-fetch';
/**
* A test helper implementation of {@link @backstage/core-plugin-api#FetchApi}.
*
* @public
*/
export class MockFetchApi implements FetchApi {
#implementation: typeof crossFetch;
/**
* Creates a {@link MockFetchApi}.
*
* @param mockImplementation - Here you can pass in a `jest.fn()` for example,
* if you want to track the calls being made through the
* {@link @backstage/core-plugin-api#MockFetchApi}. If you pass in no
* mock implementation, the created
* {@link @backstage/core-plugin-api#MockFetchApi} will make actual
* requests using the global `fetch`.
*/
constructor(implementation?: typeof crossFetch) {
this.#implementation = implementation ?? crossFetch;
}
/** {@inheritdoc @backstage/core-plugin-api#FetchApi.fetch} */
get fetch(): typeof crossFetch {
return this.#implementation;
}
/**
* Adds token based Authorization headers to requests, basically simulating
* what {@link @backstage/core-app-api#FetchMiddlewares.injectIdentityAuth}
* does.
*
* @remarks
*
* You can supply either a static mock token or a mock identity API. If
* neither is given, the static token string "mocked" is used.
*/
setAuthorization(options?: {
identityApi?: Pick<IdentityApi, 'getCredentials'>;
token?: string;
}): this {
const next = this.#implementation;
const getToken = async () => {
if (options?.token) {
return options.token;
} else if (options?.identityApi) {
const { token } = await options.identityApi.getCredentials();
return token;
}
return 'mocked';
};
this.#implementation = async (input, init) => {
const request = new Request(input, init);
const token = await getToken();
if (token && !request.headers.get('authorization')) {
request.headers.set('authorization', `Bearer ${token}`);
}
return next(request);
};
return this;
}
}
@@ -0,0 +1,17 @@
/*
* Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { MockFetchApi } from './MockFetchApi';
@@ -17,5 +17,6 @@
export * from './AnalyticsApi';
export * from './ConfigApi';
export * from './ErrorApi';
export * from './FetchApi';
export * from './PermissionApi';
export * from './StorageApi';
+1 -14
View File
@@ -14,17 +14,4 @@
* limitations under the License.
*/
/**
* Sets up handlers for request mocking
* @public
* @param worker - service worker
*/
export function setupRequestMockHandlers(worker: {
listen: (t: any) => void;
close: () => void;
resetHandlers: () => void;
}) {
beforeAll(() => worker.listen({ onUnhandledRequest: 'error' }));
afterAll(() => worker.close());
afterEach(() => worker.resetHandlers());
}
export { setupRequestMockHandlers } from './setupRequestMockHandlers';
@@ -0,0 +1,30 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Sets up handlers for request mocking
* @public
* @param worker - service worker
*/
export function setupRequestMockHandlers(worker: {
listen: (t: any) => void;
close: () => void;
resetHandlers: () => void;
}) {
beforeAll(() => worker.listen({ onUnhandledRequest: 'error' }));
afterAll(() => worker.close());
afterEach(() => worker.resetHandlers());
}