Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mocks for CoreStart, CoreSetup and PluginInitializerContext #39351

Merged
merged 18 commits into from
Jun 28, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
<b>Signature:</b>

```typescript
export declare type UiSettingsSetup = UiSettingsClient;
export declare type UiSettingsSetup = PublicMethodsOf<UiSettingsClient>;
```
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
<b>Signature:</b>

```typescript
export declare type UiSettingsStart = UiSettingsClient;
export declare type UiSettingsStart = PublicMethodsOf<UiSettingsClient>;
rudolf marked this conversation as resolved.
Show resolved Hide resolved
```
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

```typescript
config: {
create: <Schema>() => Observable<Schema>;
createIfExists: <Schema>() => Observable<Schema | undefined>;
create: <T = ConfigSchema>() => Observable<T>;
createIfExists: <T = ConfigSchema>() => Observable<T | undefined>;
};
```
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ Context that's available to plugins during initialization stage.
<b>Signature:</b>

```typescript
export interface PluginInitializerContext
export interface PluginInitializerContext<ConfigSchema = unknown>
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| [config](./kibana-plugin-server.plugininitializercontext.config.md) | <code>{</code><br/><code> create: &lt;Schema&gt;() =&gt; Observable&lt;Schema&gt;;</code><br/><code> createIfExists: &lt;Schema&gt;() =&gt; Observable&lt;Schema &#124; undefined&gt;;</code><br/><code> }</code> | |
| [config](./kibana-plugin-server.plugininitializercontext.config.md) | <code>{</code><br/><code> create: &lt;T = ConfigSchema&gt;() =&gt; Observable&lt;T&gt;;</code><br/><code> createIfExists: &lt;T = ConfigSchema&gt;() =&gt; Observable&lt;T &#124; undefined&gt;;</code><br/><code> }</code> | |
| [env](./kibana-plugin-server.plugininitializercontext.env.md) | <code>{</code><br/><code> mode: EnvironmentMode;</code><br/><code> }</code> | |
| [logger](./kibana-plugin-server.plugininitializercontext.logger.md) | <code>LoggerFactory</code> | |

41 changes: 41 additions & 0 deletions src/core/public/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
import { CoreSetup, CoreStart, PluginInitializerContext } from '.';
import { chromeServiceMock } from './chrome/chrome_service.mock';
import { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock';
import { httpServiceMock } from './http/http_service.mock';
import { i18nServiceMock } from './i18n/i18n_service.mock';
import { notificationServiceMock } from './notifications/notifications_service.mock';
import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock';
import { applicationServiceMock } from './application/application_service.mock';
import { overlayServiceMock } from './overlays/overlay_service.mock';

export { chromeServiceMock } from './chrome/chrome_service.mock';
export { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock';
Expand All @@ -25,3 +34,35 @@ export { injectedMetadataServiceMock } from './injected_metadata/injected_metada
export { legacyPlatformServiceMock } from './legacy/legacy_service.mock';
export { notificationServiceMock } from './notifications/notifications_service.mock';
export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock';
export { overlayServiceMock } from './overlays/overlay_service.mock';

function createCoreSetupMock() {
const mock: jest.Mocked<CoreSetup> = {
http: httpServiceMock.createSetupContract(),
fatalErrors: fatalErrorsServiceMock.createSetupContract(),
notifications: notificationServiceMock.createSetupContract(),
uiSettings: uiSettingsServiceMock.createSetupContract(),
};

return mock;
}

function createCoreStartMock() {
const mock: jest.Mocked<CoreStart> = {
rudolf marked this conversation as resolved.
Show resolved Hide resolved
uiSettings: uiSettingsServiceMock.createStartContract(),
application: applicationServiceMock.createStartContract(),
chrome: chromeServiceMock.createStartContract(),
http: httpServiceMock.createStartContract(),
i18n: i18nServiceMock.createStartContract(),
notifications: notificationServiceMock.createStartContract(),
overlays: overlayServiceMock.createStartContract(),
};

return mock;
}

export const coreMock = {
createSetup: createCoreSetupMock,
createStart: createCoreStartMock,
createPluginInitializerContext: jest.fn() as jest.Mock<PluginInitializerContext>,
};
3 changes: 2 additions & 1 deletion src/core/public/notifications/toasts/toasts_api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { ToastsApi } from './toasts_api';

import { uiSettingsServiceMock } from '../../ui_settings/ui_settings_service.mock';
import { i18nServiceMock } from '../../i18n/i18n_service.mock';
import { UiSettingsSetup } from '../../ui_settings';

async function getCurrentToasts(toasts: ToastsApi) {
return await toasts
Expand All @@ -33,7 +34,7 @@ async function getCurrentToasts(toasts: ToastsApi) {

function uiSettingsMock() {
const mock = uiSettingsServiceMock.createSetupContract();
(mock.get as jest.Mock<typeof mock['get']>).mockImplementation(() => (config: string) => {
(mock.get as jest.Mock<UiSettingsSetup['get']>).mockImplementation(() => (config: string) => {
switch (config) {
case 'notifications:lifetime:info':
return 5000;
Expand Down
4 changes: 4 additions & 0 deletions src/core/public/overlays/overlay_service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ const createStartContractMock = () => {
openFlyout: jest.fn(),
openModal: jest.fn(),
};
startContract.openModal.mockReturnValue({
close: jest.fn(),
onClose: Promise.resolve(),
});
return startContract;
};

Expand Down
4 changes: 2 additions & 2 deletions src/core/public/public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -459,10 +459,10 @@ export class UiSettingsClient {
}

// @public (undocumented)
export type UiSettingsSetup = UiSettingsClient;
export type UiSettingsSetup = PublicMethodsOf<UiSettingsClient>;

// @public (undocumented)
export type UiSettingsStart = UiSettingsClient;
export type UiSettingsStart = PublicMethodsOf<UiSettingsClient>;

// @public (undocumented)
export interface UiSettingsState {
Expand Down
4 changes: 2 additions & 2 deletions src/core/public/ui_settings/ui_settings_service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const createSetupContractMock = () => {
setupContract.getUpdateErrors$.mockReturnValue(new Rx.Subject<any>());

// we have to suppress type errors until decide how to mock es6 class
return (setupContract as unknown) as UiSettingsSetup;
return setupContract;
};

type UiSettingsServiceContract = PublicMethodsOf<UiSettingsService>;
Expand All @@ -53,7 +53,7 @@ const createMock = () => {
stop: jest.fn(),
};

mocked.setup.mockReturnValue(createSetupContractMock());
mocked.setup.mockReturnValue((createSetupContractMock() as unknown) as UiSettingsSetup);
return mocked;
};

Expand Down
4 changes: 2 additions & 2 deletions src/core/public/ui_settings/ui_settings_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export class UiSettingsService {
}

/** @public */
export type UiSettingsSetup = UiSettingsClient;
export type UiSettingsSetup = PublicMethodsOf<UiSettingsClient>;

/** @public */
export type UiSettingsStart = UiSettingsClient;
export type UiSettingsStart = PublicMethodsOf<UiSettingsClient>;
1 change: 1 addition & 0 deletions src/core/server/http/http_service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,5 @@ const createHttpServiceMock = () => {
export const httpServiceMock = {
create: createHttpServiceMock,
createSetupContract: createSetupContractMock,
createStartContract: createStartContractMock,
};
55 changes: 55 additions & 0 deletions src/core/server/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,64 @@
* specific language governing permissions and limitations
* under the License.
*/
import { of } from 'rxjs';
import { PluginInitializerContext, CoreSetup, CoreStart } from '.';
import { loggingServiceMock } from './logging/logging_service.mock';
import { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock';
import { httpServiceMock } from './http/http_service.mock';

export { configServiceMock } from './config/config_service.mock';
export { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock';
export { httpServiceMock } from './http/http_service.mock';
export { loggingServiceMock } from './logging/logging_service.mock';
export { SavedObjectsClientMock } from './saved_objects/service/saved_objects_client.mock';

export function pluginInitializerContextConfigMock<T>(defaultValue: T) {
return (value?: Partial<T>) => {
rudolf marked this conversation as resolved.
Show resolved Hide resolved
const config: T = Object.assign({}, defaultValue, value);

const mock: jest.Mocked<PluginInitializerContext<T>['config']> = {
create: jest.fn().mockReturnValue(of(config)),
createIfExists: jest.fn().mockReturnValue(of(config)),
};

return mock;
};
}

function pluginInitializerContextMock<T>(config: T) {
const mock: jest.Mocked<PluginInitializerContext<T>> = {
logger: loggingServiceMock.create(),
env: {
mode: {
dev: true,
name: 'development',
prod: false,
},
},
config: pluginInitializerContextConfigMock(config)() as jest.Mocked<
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, since the only place this is called is right here and the return value is used right away, why not just make it

export function createPluginInitializerContextConfigMock<T>(config: T) {
     const mock: jest.Mocked<PluginInitializerContext<T>['config']> = {
      create: jest.fn().mockReturnValue(of(config)),
      createIfExists: jest.fn().mockReturnValue(of(config)),
    };

     return mock;
}

But I notice you export that file so maybe you assume it will likely be used elsewhere, and in those cases, passed in a value parameter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I based this on the tests in https://github.com/toddself/kibana/blob/proxy/x-pack/plugins/proxy/server/cluster_doc.test.ts#L23-L59 (not yet merged into master), which used default and then specific overrides for each of the tests.

Though looking at this again, it feels like this is easy enough for the specific test to implement themselves if they need it. It makes the mock a little weird to consume and developers will probably need to look at the source code to understand the signature, at which point having an additional Object.assign({}, mydefaults, value) is probably much easier to follow for anyone reading the code.

PluginInitializerContext<T>['config']
>,
};

return mock.config;
}

function createCoreSetupMock() {
const mock: jest.Mocked<CoreSetup> = {
elasticsearch: elasticsearchServiceMock.createSetupContract(),
http: httpServiceMock.createSetupContract(),
};

return mock;
}

function createCoreStartMock() {
return {} as jest.Mocked<CoreStart>;
}

export const coreMock = {
createSetup: createCoreSetupMock,
createStart: createCoreStartMock,
createPluginInitializerContext: pluginInitializerContextMock,
};
6 changes: 3 additions & 3 deletions src/core/server/plugins/plugin_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ import { CoreSetup, CoreStart } from '..';
*
* @public
*/
export interface PluginInitializerContext {
export interface PluginInitializerContext<ConfigSchema = unknown> {
env: { mode: EnvironmentMode };
logger: LoggerFactory;
config: {
create: <Schema>() => Observable<Schema>;
createIfExists: <Schema>() => Observable<Schema | undefined>;
create: <T = ConfigSchema>() => Observable<T>;
createIfExists: <T = ConfigSchema>() => Observable<T | undefined>;
};
}

Expand Down
6 changes: 3 additions & 3 deletions src/core/server/server.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,11 +328,11 @@ export interface Plugin<TSetup, TStart, TPluginsSetup extends Record<PluginName,
export type PluginInitializer<TSetup, TStart, TPluginsSetup extends Record<PluginName, unknown> = {}, TPluginsStart extends Record<PluginName, unknown> = {}> = (core: PluginInitializerContext) => Plugin<TSetup, TStart, TPluginsSetup, TPluginsStart>;

// @public
export interface PluginInitializerContext {
export interface PluginInitializerContext<ConfigSchema = unknown> {
// (undocumented)
config: {
create: <Schema>() => Observable<Schema>;
createIfExists: <Schema>() => Observable<Schema | undefined>;
create: <T = ConfigSchema>() => Observable<T>;
createIfExists: <T = ConfigSchema>() => Observable<T | undefined>;
};
// (undocumented)
env: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,15 @@
* under the License.
*/

import { fatalErrorsServiceMock, notificationServiceMock } from '../../../../core/public/mocks';
import { coreMock } from '../../../../core/public/mocks';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about having this file also be in NP so we don't even have to duplicate this everywhere? Then in your actual test files, you'd just import something like

import { coreMock } from '../../../../core/public/np_core.test.mocks';?

... though I suppose in this case I wouldn't want to mock the overlay functions for everyone... But it does seem like in most cases, just importing the single file is enough for folks who don't have to mock anything particular. 🤔 Just a thought!


let modalContents: React.Component;

export const getModalContents = () => modalContents;
export const coreSetupMock = coreMock.createSetup();
export const coreStartMock = coreMock.createStart();

jest.doMock('ui/new_platform', () => {
return {
npStart: {
core: {
overlays: {
openFlyout: jest.fn(),
openModal: (component: React.Component) => {
modalContents = component;
return {
close: jest.fn(),
};
},
},
},
},
npSetup: {
core: {
fatalErrors: fatalErrorsServiceMock.createSetupContract(),
notifications: notificationServiceMock.createSetupContract(),
},
},
npStart: { core: coreStartMock },
npSetup: { core: coreSetupMock },
};
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/

import { getModalContents } from '../../../../np_core.test.mocks';
import '../../../../np_core.test.mocks';
rudolf marked this conversation as resolved.
Show resolved Hide resolved

import React from 'react';
import {
Expand All @@ -36,6 +35,8 @@ import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers';
import { skip } from 'rxjs/operators';
import * as Rx from 'rxjs';
import { EmbeddableFactory } from '../../../../embeddables';
import { coreStartMock } from '../../../../np_core.test.mocks';
import { OverlayStart } from 'src/core/public';

const onClose = jest.fn();
let container: Container;
Expand Down Expand Up @@ -79,7 +80,8 @@ test('create new calls factory.adds a panel to the container', async done => {

await nextTick();

(getModalContents().props as ContactCardInitializerProps).onCreate({
const overlayMock = (coreStartMock.overlays as unknown) as jest.Mocked<OverlayStart>;
((overlayMock.openModal.mock.calls[0][0] as any).props as ContactCardInitializerProps).onCreate({
firstName: 'Dany',
lastName: 'Targaryan',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
*/

import sinon from 'sinon';
import { ToastsApi } from '../../../../../core/public';
import { ToastsApi, UiSettingsSetup } from '../../../../../core/public';
import { uiSettingsServiceMock, i18nServiceMock } from '../../../../../core/public/mocks';
import { ToastNotifications } from './toast_notifications';

function toastDeps() {
const uiSettingsMock = uiSettingsServiceMock.createSetupContract();
(uiSettingsMock.get as jest.Mock<typeof uiSettingsMock['get']>).mockImplementation(
(uiSettingsMock.get as jest.Mock<UiSettingsSetup['get']>).mockImplementation(
rudolf marked this conversation as resolved.
Show resolved Hide resolved
() => (config: string) => {
switch (config) {
case 'notifications:lifetime:info':
Expand Down