Skip to content

Commit

Permalink
Test docs for Saved Objects unit and integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
rudolf committed Feb 6, 2020
1 parent a5c2603 commit 999af60
Showing 1 changed file with 231 additions and 11 deletions.
242 changes: 231 additions & 11 deletions src/core/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,34 @@

This document outlines best practices and patterns for testing Kibana Plugins.

- [Strategy](#strategy)
- [Core Integrations](#core-integrations)
- [Core Mocks](#core-mocks)
- [Testing Kibana Plugins](#testing-kibana-plugins)
- [Strategy](#strategy)
- [New concerns in the Kibana Platform](#new-concerns-in-the-kibana-platform)
- [Core Integrations](#core-integrations)
- [Core Mocks](#core-mocks)
- [Example](#example)
- [Strategies for specific Core APIs](#strategies-for-specific-core-apis)
- [HTTP Routes](#http-routes)
- [SavedObjects](#savedobjects)
- [Elasticsearch](#elasticsearch)
- [Plugin Integrations](#plugin-integrations)
- [Plugin Contracts](#plugin-contracts)
- [HTTP Routes](#http-routes)
- [Preconditions](#preconditions)
- [Unit testing](#unit-testing)
- [Example](#example-1)
- [Integration tests](#integration-tests)
- [Functional Test Runner](#functional-test-runner)
- [Example](#example-2)
- [TestUtils](#testutils)
- [Example](#example-3)
- [Applications](#applications)
- [Example](#example-4)
- [SavedObjects](#savedobjects)
- [Unit Tests](#unit-tests)
- [Integration Tests](#integration-tests-1)
- [Elasticsearch](#elasticsearch)
- [Plugin integrations](#plugin-integrations)
- [Preconditions](#preconditions-1)
- [Testing dependencies usages](#testing-dependencies-usages)
- [Testing components consuming the dependencies](#testing-components-consuming-the-dependencies)
- [Testing optional plugin dependencies](#testing-optional-plugin-dependencies)
- [Plugin Contracts](#plugin-contracts)

## Strategy

Expand Down Expand Up @@ -540,11 +559,212 @@ describe('renderApp', () => {
});
```
#### SavedObjects
### SavedObjects
_How to test SO operations_
#### Unit Tests
#### Elasticsearch
To unit test code that uses the Saved Objects client mock the client methods
and make assertions against the behaviour you would expect to see.
Since the Saved Objects client makes network requests to an external
Elasticsearch cluster, it's important to include failure scenarios in your
test cases.
When writing a view with which a user might interact, it's important to ensure
your code can recover from exceptions and provide a way for the user to
proceed. This behaviour should be tested as well.
Below is an example of a Jest Unit test suite that mocks the server-side Saved
Objects client:
```typescript
// src/plugins/myplugin/server/lib/short_url_lookup.ts
import crypto from 'crypto';
import { SavedObjectsClientContract } from 'kibana/server';

export const shortUrlLookup = {
generateUrlId(url: string, savedObjectsClient: SavedObjectsClientContract) {
const id = crypto
.createHash('md5')
.update(url)
.digest('hex');

return savedObjectsClient
.create(
'url',
{
url,
accessCount: 0,
createDate: new Date().valueOf(),
accessDate: new Date().valueOf(),
},
{ id }
)
.then(doc => doc.id)
.catch(err => {
if (savedObjectsClient.errors.isConflictError(err)) {
return id;
} else {
throw err;
}
});
},
};

```
```typescript
// src/plugins/myplugin/server/lib/short_url_lookup.test.ts
import { shortUrlLookup } from './short_url_lookup';
import { savedObjectsClientMock } from '../../../../../core/server/mocks';

describe('shortUrlLookup', () => {
const ID = 'bf00ad16941fc51420f91a93428b27a0';
const TYPE = 'url';
const URL = 'http://elastic.co';

const mockSavedObjectsClient = savedObjectsClientMock.create();

beforeEach(() => {
jest.resetAllMocks();
});

describe('generateUrlId', () => {
it('provides correct arguments to savedObjectsClient', async () => {
const ATTRIBUTES = {
url: URL,
accessCount: 0,
createDate: new Date().valueOf(),
accessDate: new Date().valueOf(),
};
mockSavedObjectsClient.create.mockResolvedValueOnce({
id: ID,
type: TYPE,
references: [],
attributes: ATTRIBUTES,
});
await shortUrlLookup.generateUrlId(URL, mockSavedObjectsClient);

expect(mockSavedObjectsClient.create).toHaveBeenCalledTimes(1);
const [type, attributes, options] = mockSavedObjectsClient.create.mock.calls[0];
expect(type).toBe(TYPE);
expect(attributes).toStrictEqual(ATTRIBUTES);
expect(options).toStrictEqual({ id: ID });
});

it('ignores version conflict and returns id', async () => {
mockSavedObjectsClient.create.mockRejectedValueOnce(
mockSavedObjectsClient.errors.decorateConflictError(new Error())
);
const id = await shortUrlLookup.generateUrlId(URL, mockSavedObjectsClient);
expect(id).toEqual(ID);
});

it('rejects with passed through savedObjectsClient errors', () => {
const error = new Error('oops');
mockSavedObjectsClient.create.mockRejectedValueOnce(error);
return expect(shortUrlLookup.generateUrlId(URL, mockSavedObjectsClient)).rejects.toBe(error);
});
});
});
```
The following is an example of a public saved object unit test. The biggest
difference with the server-side test is the slightly different Saved Objects
client API which returns `SimpleSavedObject` instances which needs to be
reflected in the mock.
```typescript
// src/plugins/myplugin/public/saved_query_service.ts
import { SavedObjectsClientContract, SavedObjectAttributes } from 'src/core/public';

export const createSavedQueryService = (savedObjectsClient: SavedObjectsClientContract) => {
const saveQuery = async (attributes: SavedObjectAttributes) => {
try {
return await savedObjectsClient.create('query', attributes, {
id: attributes.title as string,
});
} catch (err) {
throw new Error('Unable to create saved query, please try again.');
}
};

return {
saveQuery,
};
};
```
```typescript
// src/plugins/myplugin/public/saved_query_service.test.ts
import { createSavedQueryService } from './saved_query_service';
import { savedObjectsServiceMock } from '../../../core/public/mocks';
import { SavedObjectsClientContract, SimpleSavedObject } from '../../../core/public';

describe('saved query service', () => {
const savedQueryAttributes = {
title: 'foo',
description: 'bar',
query: {
language: 'kuery',
query: 'response:200',
},
};

const mockSavedObjectsClient = savedObjectsServiceMock.createStartContract()
.client as jest.Mocked<SavedObjectsClientContract>;

const savedQueryService = createSavedQueryService(mockSavedObjectsClient);

afterEach(() => {
jest.resetAllMocks();
});

describe('saveQuery', function() {
it('should create a saved object for the given attributes', async () => {
// The public Saved Objects client returns instances of
// SimpleSavedObject, so we create an instance to return from our mock.
const mockReturnValue = new SimpleSavedObject(mockSavedObjectsClient, {
type: 'query',
id: 'foo',
attributes: savedQueryAttributes,
references: [],
});
mockSavedObjectsClient.create.mockResolvedValue(mockReturnValue);

const response = await savedQueryService.saveQuery(savedQueryAttributes);
expect(mockSavedObjectsClient.create).toHaveBeenCalledWith('query', savedQueryAttributes, {
id: 'foo',
});
expect(response).toBe(mockReturnValue);
});

it('should reject with an error when saved objects client errors', () => {
mockSavedObjectsClient.create.mockRejectedValue(new Error('timeout'));

return expect(
savedQueryService.saveQuery(savedQueryAttributes)
).rejects.toMatchInlineSnapshot(`[Error: Unable to create saved query, please try again.]`);
});
});
});
```
#### Integration Tests
To get the highest confidence in how your code behaves when using the Saved
Objects client, you should write at least a few integration tests which loads
data into and queries a real Elasticsearch database.
To do that we'll write a Jest integration test using `TestUtils` to start
Kibana and esArchiver to load fixture data into Elasticsearch.
1. Create the fixtures data you need in Elasticsearch
2. Create a fixtures archive with `node scripts/es_archiver save <name> [index patterns...]`
3. Load the fixtures in your test using esArchiver `esArchiver.load('name')`;
_todo: fully worked out example_
### Elasticsearch
_How to test ES clients_
Expand Down

0 comments on commit 999af60

Please sign in to comment.