@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/frontend-test-utils': patch
|
||||
---
|
||||
|
||||
Added an `ApiMock`, analogous to `ServiceMock` from the backend test utils.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': minor
|
||||
---
|
||||
|
||||
Add catalog service mocks under the `/testUtils` subpath export.
|
||||
|
||||
You can now use e.g. `const catalog = catalogApiMock.mock()` in your test and then do assertions on `catalog.getEntities` without awkward type casting.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-node': patch
|
||||
---
|
||||
|
||||
Documentation for the `testUtils` named export
|
||||
@@ -3,11 +3,13 @@
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
/// <reference types="jest" />
|
||||
/// <reference types="react" />
|
||||
|
||||
import { AnalyticsApi } from '@backstage/frontend-plugin-api';
|
||||
import { AnalyticsEvent } from '@backstage/frontend-plugin-api';
|
||||
import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api';
|
||||
import { ApiFactory } from '@backstage/frontend-plugin-api';
|
||||
import { AppNode } from '@backstage/frontend-plugin-api';
|
||||
import { AppNodeInstance } from '@backstage/frontend-plugin-api';
|
||||
import { ErrorWithContext } from '@backstage/test-utils';
|
||||
@@ -32,6 +34,15 @@ import { TestApiProviderProps } from '@backstage/test-utils';
|
||||
import { TestApiRegistry } from '@backstage/test-utils';
|
||||
import { withLogCollector } from '@backstage/test-utils';
|
||||
|
||||
// @public
|
||||
export type ApiMock<TApi> = {
|
||||
factory: ApiFactory<TApi, TApi, {}>;
|
||||
} & {
|
||||
[Key in keyof TApi]: TApi[Key] extends (...args: infer Args) => infer Return
|
||||
? TApi[Key] & jest.MockInstance<Return, Args>
|
||||
: TApi[Key];
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export function createExtensionTester<T extends ExtensionDefinitionParameters>(
|
||||
subject: ExtensionDefinition<T>,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2024 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 { ApiFactory } from '@backstage/frontend-plugin-api';
|
||||
|
||||
/**
|
||||
* Represents a mocked version of an API, where you automatically have access to
|
||||
* the mocked versions of all of its methods along with a factory that returns
|
||||
* that same mock.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ApiMock<TApi> = {
|
||||
factory: ApiFactory<TApi, TApi, {}>;
|
||||
} & {
|
||||
[Key in keyof TApi]: TApi[Key] extends (...args: infer Args) => infer Return
|
||||
? TApi[Key] & jest.MockInstance<Return, Args>
|
||||
: TApi[Key];
|
||||
};
|
||||
@@ -26,4 +26,5 @@ export {
|
||||
type MockStorageBucket,
|
||||
} from '@backstage/test-utils';
|
||||
|
||||
export { type ApiMock } from './ApiMock';
|
||||
export { MockAnalyticsApi } from './AnalyticsApi/MockAnalyticsApi';
|
||||
|
||||
@@ -14,4 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Backend test helpers for the Catalog plugin.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export { catalogServiceMock } from './testUtils/catalogServiceMock';
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"@backstage/core-plugin-api": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/frontend-plugin-api": "workspace:^",
|
||||
"@backstage/frontend-test-utils": "workspace:^",
|
||||
"@backstage/integration-react": "workspace:^",
|
||||
"@backstage/plugin-catalog-common": "workspace:^",
|
||||
"@backstage/plugin-permission-common": "workspace:^",
|
||||
@@ -89,7 +90,6 @@
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/core-app-api": "workspace:^",
|
||||
"@backstage/frontend-test-utils": "workspace:^",
|
||||
"@backstage/plugin-catalog-common": "workspace:^",
|
||||
"@backstage/plugin-scaffolder-common": "workspace:^",
|
||||
"@backstage/test-utils": "workspace:^",
|
||||
|
||||
@@ -3,11 +3,28 @@
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { ApiFactory } from '@backstage/frontend-plugin-api';
|
||||
import { ApiMock } from '@backstage/frontend-test-utils';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { DefaultEntityFilters } from '@backstage/plugin-catalog-react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityListContextProps } from '@backstage/plugin-catalog-react';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { default as React_2 } from 'react';
|
||||
|
||||
// @public
|
||||
export function catalogApiMock(options?: { entities?: Entity[] }): CatalogApi;
|
||||
|
||||
// @public
|
||||
export namespace catalogApiMock {
|
||||
const factory: (options?: {
|
||||
entities?: Entity[];
|
||||
}) => ApiFactory<CatalogApi, CatalogApi, {}>;
|
||||
const mock: (
|
||||
partialImpl?: Partial<CatalogApi> | undefined,
|
||||
) => ApiMock<CatalogApi>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export function MockEntityListContextProvider<
|
||||
T extends DefaultEntityFilters = DefaultEntityFilters,
|
||||
|
||||
@@ -20,4 +20,5 @@
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export { catalogApiMock } from './testUtils/catalogApiMock';
|
||||
export { MockEntityListContextProvider } from './testUtils/MockEntityListContextProvider';
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2024 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 { Entity } from '@backstage/catalog-model';
|
||||
import { catalogApiMock } from './catalogApiMock';
|
||||
|
||||
const entity1: Entity = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'CustomKind',
|
||||
metadata: {
|
||||
namespace: 'default',
|
||||
name: 'e1',
|
||||
uid: 'u1',
|
||||
},
|
||||
};
|
||||
|
||||
const entity2: Entity = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'CustomKind',
|
||||
metadata: {
|
||||
namespace: 'default',
|
||||
name: 'e2',
|
||||
uid: 'u2',
|
||||
},
|
||||
};
|
||||
|
||||
const entities = [entity1, entity2];
|
||||
|
||||
describe('catalogApiMock', () => {
|
||||
it('exports the expected functionality', async () => {
|
||||
const emptyFake = catalogApiMock();
|
||||
const notEmptyFake = catalogApiMock({ entities });
|
||||
|
||||
await expect(emptyFake.getEntities()).resolves.toEqual({ items: [] });
|
||||
await expect(notEmptyFake.getEntities()).resolves.toEqual({
|
||||
items: entities,
|
||||
});
|
||||
|
||||
const mock = catalogApiMock.mock();
|
||||
expect(mock.getEntities).toHaveBeenCalledTimes(0);
|
||||
expect(mock.getEntities()).toBeUndefined();
|
||||
mock.getEntities.mockResolvedValue({ items: entities });
|
||||
await expect(mock.getEntities()).resolves.toEqual({ items: entities });
|
||||
|
||||
const mock2 = catalogApiMock.mock({
|
||||
getEntities: async () => ({ items: [entity1] }),
|
||||
});
|
||||
await expect(mock2.getEntities()).resolves.toEqual({ items: [entity1] });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2024 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 {
|
||||
ApiFactory,
|
||||
ApiRef,
|
||||
createApiFactory,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { InMemoryCatalogClient } from '@backstage/catalog-client/testUtils';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { ApiMock } from '@backstage/frontend-test-utils';
|
||||
|
||||
/** @internal */
|
||||
function simpleMock<TApi>(
|
||||
ref: ApiRef<TApi>,
|
||||
mockFactory: () => jest.Mocked<TApi>,
|
||||
): (partialImpl?: Partial<TApi>) => ApiMock<TApi> {
|
||||
return partialImpl => {
|
||||
const mock = mockFactory();
|
||||
if (partialImpl) {
|
||||
for (const [key, impl] of Object.entries(partialImpl)) {
|
||||
if (typeof impl === 'function') {
|
||||
(mock as any)[key].mockImplementation(impl);
|
||||
} else {
|
||||
(mock as any)[key] = impl;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.assign(mock, {
|
||||
factory: createApiFactory({
|
||||
api: ref,
|
||||
deps: {},
|
||||
factory: () => mock,
|
||||
}),
|
||||
}) as ApiMock<TApi>;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a fake catalog client that handles entities in memory storage. Note
|
||||
* that this client may be severely limited in functionality, and advanced
|
||||
* functions may not be available at all.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function catalogApiMock(options?: { entities?: Entity[] }): CatalogApi {
|
||||
return new InMemoryCatalogClient(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of mock functionality for the catalog service.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export namespace catalogApiMock {
|
||||
/**
|
||||
* Creates a fake catalog client that handles entities in memory storage. Note
|
||||
* that this client may be severely limited in functionality, and advanced
|
||||
* functions may not be available at all.
|
||||
*/
|
||||
export const factory = (options?: {
|
||||
entities?: Entity[];
|
||||
}): ApiFactory<CatalogApi, CatalogApi, {}> =>
|
||||
createApiFactory({
|
||||
api: catalogApiRef,
|
||||
deps: {},
|
||||
factory: () => new InMemoryCatalogClient(options),
|
||||
});
|
||||
/**
|
||||
* Creates a catalog client whose methods are mock functions, possibly with
|
||||
* some of them overloaded by the caller.
|
||||
*/
|
||||
export const mock = simpleMock(catalogApiRef, () => ({
|
||||
getEntities: jest.fn(),
|
||||
getEntitiesByRefs: jest.fn(),
|
||||
queryEntities: jest.fn(),
|
||||
getEntityAncestors: jest.fn(),
|
||||
getEntityByRef: jest.fn(),
|
||||
removeEntityByUid: jest.fn(),
|
||||
refreshEntity: jest.fn(),
|
||||
getEntityFacets: jest.fn(),
|
||||
getLocationById: jest.fn(),
|
||||
getLocationByRef: jest.fn(),
|
||||
addLocation: jest.fn(),
|
||||
removeLocationById: jest.fn(),
|
||||
getLocationByEntity: jest.fn(),
|
||||
validateEntity: jest.fn(),
|
||||
}));
|
||||
}
|
||||
Reference in New Issue
Block a user