Giter Site home page Giter Site logo

elastic / elasticsearch-js-mock Goto Github PK

View Code? Open in Web Editor NEW
45.0 5.0 10.0 94 KB

Mock utility for the Elasticsearch's Node.js client

Home Page: https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/client-testing.html

License: Apache License 2.0

JavaScript 93.03% TypeScript 6.97%
elasticsearch nodejs client rest mock testing-tools

elasticsearch-js-mock's Introduction

Elasticsearch Node.js client mock utility

js-standard-style build

When testing your application you don't always need to have an Elasticsearch instance up and running, but you might still need to use the client for fetching some data. If you are facing this situation, this library is what you need.

Use v1.0.0 for @elastic/elasticsearch โ‰ค v7 compatibility and v2.0.0 for @elastic/elasticsearch โ‰ฅ v8 compatibility.

Features

  • Simple and intuitive API
  • Mocks only the http layer, leaving the rest of the client working as usual
  • Maximum flexibility thanks to "strict" or "loose" mocks

Install

npm install @elastic/elasticsearch-mock --save-dev

Usage

const { Client } = require('@elastic/elasticsearch')
const Mock = require('@elastic/elasticsearch-mock')

const mock = new Mock()
const client = new Client({
  node: 'http://localhost:9200',
  Connection: mock.getConnection()
})

mock.add({
  method: 'GET',
  path: '/_cat/health'
}, () => {
  return { status: 'ok' }
})

client.cat.health()
  .then(console.log)
  .catch(console.log)

API

Constructor

Before start using the library you need to create a new instance:

const Mock = require('@elastic/elasticsearch-mock')
const mock = new Mock()

add

Adds a new mock for a given pattern and assigns it to a resolver function.

// every GET request to the `/_cat/health` path
// will return `{ status: 'ok' }`
mock.add({
  method: 'GET',
  path: '/_cat/health'
}, () => {
  return { status: 'ok' }
})

You can also specify multiple methods and/or paths at the same time:

// This mock will catch every search request against any index
mock.add({
  method: ['GET', 'POST'],
  path: ['/_search', '/:index/_search']
}, () => {
  return { status: 'ok' }
})

get

Returns the matching resolver function for the given pattern, it returns null if there is not a matching pattern.

const fn = mock.get({
  method: 'GET',
  path: '/_cat/health'
})

clear

Clears/removes mocks for specific route(s).

mock.clear({
  method: ['GET'],
  path: ['/_search', '/:index/_search']
})

clearAll

Clears all mocks.

mock.clearAll()

getConnection

Returns a custom Connection class that you must pass to the Elasticsearch client instance.

const { Client } = require('@elastic/elasticsearch')
const Mock = require('@elastic/elasticsearch-mock')

const mock = new Mock()
const client = new Client({
  node: 'http://localhost:9200',
  Connection: mock.getConnection()
})

Mock patterns

A pattern is an object that describes an http query to Elasticsearch, and it looks like this:

interface MockPattern {
  method: string
  path: string
  querystring?: Record<string, string>
  body?: Record<string, any>
}

The more field you specify, the more the mock will be strict, for example:

mock.add({
  method: 'GET',
  path: '/_cat/health'
  querystring: { pretty: 'true' }
}, () => {
  return { status: 'ok' }
})

client.cat.health()
  .then(console.log)
  .catch(console.log) // 404 error

client.cat.health({ pretty: true })
  .then(console.log) // { status: 'ok' }
  .catch(console.log)

You can craft custom responses for different queries:

mock.add({
  method: 'POST',
  path: '/indexName/_search'
  body: { query: { match_all: {} } }
}, () => {
  return {
    hits: {
      total: { value: 1, relation: 'eq' },
      hits: [{ _source: { baz: 'faz' } }]
    }
  }
})

mock.add({
  method: 'POST',
  path: '/indexName/_search',
  body: { query: { match: { foo: 'bar' } } }
}, () => {
  return {
    hits: {
      total: { value: 0, relation: 'eq' },
      hits: []
    }
  }
})

You can also specify dynamic urls:

mock.add({
  method: 'GET',
  path: '/:index/_count'
}, () => {
  return { count: 42 }
})

client.count({ index: 'foo' })
  .then(console.log) // => { count: 42 }
  .catch(console.log)

client.count({ index: 'bar' })
  .then(console.log) // => { count: 42 }
  .catch(console.log)

Wildcards are supported as well.

mock.add({
  method: 'HEAD',
  path: '*'
}, () => {
  return ''
})

client.indices.exists({ index: 'foo' })
  .then(console.log) // => true
  .catch(console.log)

client.ping()
  .then(console.log) // => true
  .catch(console.log)

Dynamic responses

The resolver function takes a single parameter which represent the API call that has been made by the client. You can use it to craft dynamic responses.

mock.add({
  method: 'POST',
  path: '/indexName/_search',
}, params => {
  return { query: params.body.query }
})

Errors

This utility uses the same error classes of the Elasticsearch client, if you want to return an error for a specific API call, you should use the ResponseError class:

const { errors } = require('@elastic/elasticsearch')
const Mock = require('@elastic/elasticsearch-mock')

const mock = new Mock()
mock.add({
  method: 'GET',
  path: '/_cat/health'
}, () => {
  return new errors.ResponseError({
    body: { errors: {}, status: 500 },
    statusCode: 500
  })
})

License

This software is licensed under the Apache 2 license.

elasticsearch-js-mock's People

Contributors

delvedor avatar jacse avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

elasticsearch-js-mock's Issues

Same request with no data and with data gets the same result even if they don't have same response.

Hello, I'm facing this issue.
Right now I'm trying to get different response for same verb with different query, but I always get the response for the one that do not have query content:

mock.add({
  method: 'GET',
  path: '/index/_search'
}, () => {
  return {
    hits: {
      total: { value: 1, relation: 'eq' },
      hits: [{ _source: { baz: 'faz' } }]
    }
  }
})
mock.add({
  method: 'GET',
  path: '/index/_search',
  querystring: { pretty: 'true' }
}, () => {
  return {
    hits: {
      total: { value: 1, relation: 'eq' },
      hits: [
       { _source: { baz: 'new' } },
       { _source: { baz: 'test' } }
     ]
    }
  }
})

So, client.search({ index: 'test' })
and client.search({ index: 'test', querystring: { pretty: 'true' }})
always gave me

hits: {
      total: { value: 1, relation: 'eq' },
      hits: [{ _source: { baz: 'faz' } }]
    }
  }

can someone help me with this?

How to create ResponseErrors in TypeScript

Hi, thanks for this excellent library.

I'm trying to mock specific ResponseErrors in TypeScript but I'm getting stuck. The ResponseError constructor takes a huge amount of arguments that I'm unable to workaround without the compiler complaining or it not working.

This is as far as I've got:

import ClientMock from "@elastic/elasticsearch-mock";
import { Client, Connection } from "@elastic/elasticsearch";
import { ResponseError } from "@elastic/elasticsearch/lib/errors";

type RootCause = {
    type: string;
    reason: string;
};

export function addResponseError(
    clientMock: ClientMock,
    method: string,
    path: string,
    statusCode: number,
    rootCauses: RootCause[]
): void {
    const connection = clientMock.getConnection();
    clientMock.add(
        {
            method,
            path,
        },
        () => {
            return new ResponseError({
                body: {
                    errors: {
                        root_cause: rootCauses,
                    },
                    status: statusCode
                },
                statusCode,
                headers: {},
                meta: {
                    name: "foo",
                    context: {},
                    aborted: false,
                    attempts: 0,
                    request: {
                        id: 1,
                        options: {},
                        params: {
                            method,
                            path,
                        },
                    },
                    connection,
                },
                warnings: [],
            });
        }
    );
}

I had previously set connection to {} as Connection but I received no response in my test, so I thought I could use the one from ClientMock.getConnection() instead. However, I get an error:

Type 'typeof Connection' is missing the following properties from type 'Connection': url, ssl, id, headers, and 13 more.ts(2740)
Transport.d.ts(63, 5): The expected type comes from property 'connection' which is declared here on type '{ context: Context; name: string | symbol; request: { params: TransportRequestParams; options: TransportRequestOptions; id: any; }; connection: Connection; attempts: number; aborted: boolean; sniff?: { ...; } | undefined; }'

Any suggestions?

ProductNotSupportedError for elasticsearch-js-mock >=v0.3.1 <=1.0.0 and @elasticsearch/elasticsearch >7.13.0 <= 7.17.0

We have a set of Elasticsearch client unit tests which make use of the mock interface. These tests were previously working using elasticsearch-js-mock v0.3.1 and an old version of Elasticsearchclient 7.6.1. Upon upgrading to the latest ES client 7.17, we also brought up the mock version to 1.0.0 (as it says it works with ES 7 clients). However, this causes the error:

ProductNotSupportedError: The client noticed that the server is not Elasticsearch and we do not support this unknown product.

to appear when the client getMapping endpoint is called. Further investigation shows that this error occurs for both elasticsearch-js-mock v0.3.1 and v1.0.0, with all elasticsearch 7 clients greater than 7.13.0. It appears that this problem was addressed a while ago for earlier versions of Elasticsearch, and thus 0.3.1 was born; could something have happened to break this version check with the latest versions of the Elasticsearchclient?

Full stack trace:
`
ProductNotSupportedError: The client noticed that the server is not Elasticsearch and we do not support this unknown product.

  75 | function getMappingFromElastic(params) {
  76 |     return __awaiter(this, void 0, void 0, function* () {
> 77 |         return client.indices.getMapping(params);
     |                               ^
  78 |     });
  79 | }
  80 | exports.getMappingFromElastic = getMappingFromElastic;

  at Transport.request (node_modules/@elastic/elasticsearch/lib/Transport.js:477:19)
  at IndicesApi.indicesGetMappingApi [as getMapping] (node_modules/@elastic/elasticsearch/api/api/indices.js:845:25)
  at getMapping (build/elasticsearch/elasticsearchClient.js:77:31)
  at next (build/elasticsearch/elasticsearchClient.js:8:71)
  at Object.<anonymous>.__awaiter (build/elasticsearch/elasticsearchClient.js:4:12)
  at __awaiter (build/elasticsearch/elasticsearchClient.js:76:12)
  at ElasticsearchMappingHelper.<anonymous> (build/elasticsearch/elasticsearchMappingHelper.js:35:83)
  at next (build/elasticsearch/elasticsearchMappingHelper.js:8:71)
  at Object.<anonymous>.__awaiter (build/elasticsearch/elasticsearchMappingHelper.js:4:12)
  at ElasticsearchMappingHelper.__awaiter [as getFieldNames] (build/elasticsearch/elasticsearchMappingHelper.js:29:16)
  at getFieldNames (build/elasticsearch/__testing__/elasticsearchMappingHelper.test.js:43:24)
  at next (build/elasticsearch/__testing__/elasticsearchMappingHelper.test.js:8:71)
  at Object.<anonymous>.__awaiter (build/elasticsearch/__testing__/elasticsearchMappingHelper.test.js:4:12)
  at Object.__awaiter (build/elasticsearch/__testing__/elasticsearchMappingHelper.test.js:42:40)

`

elastic mocks timeout when used with jest fake timers

Hi there ๐Ÿ‘‹๐Ÿป

Elasticsearch mocks don't work with jest fake timers and the jest test runner seems to just time out when I use fake timers

To reproduce the issue please check this codesandbox

// uncomment the following 2 lines and the tests will timeout and fail
// jest.useFakeTimers("modern");
// jest.setSystemTime(new Date());

it("should run", async () => {
  // when using fake timers you can use the following line to get the test to run correctly
  // jest.useRealTimers();
  const { Client } = require("@elastic/elasticsearch");
  const Mock = require("@elastic/elasticsearch-mock");
  const elasticMock = new Mock();
  const client = new Client({
    node: "http://localhost:9200",
    Connection: elasticMock.getConnection()
  });
  elasticMock.add({ method: "GET", path: "/_cat/health" }, () => ({
    status: "ok"
  }));
  const fake = await client.cat.health();
  expect(fake.status).toEqual("ok");
});

We push data to elastic search as a callback / side effect for write operations on some models, some of which are time sensitive... we use fake timers to help us tests different scenarios.

Is there a way where we can use elasticsearch-mock with jest fake timers or any other timer alternatives ?

Support for multiple definitions

It can happen that a. user needs to mock in the same way different methods/paths, we should provide a nice way to do so. For example:

mock.add({
  method: ['GET', 'HEAD'],
  path: '/'
}, () => {
  return { status: 'ok' }
})

Making assertions about how the mock was used

Particularly when using wildcards, I might want to make assertions about how the client was used.

Sample (with workaround to show want I want to do - should run with npx ts-node if the client and the mock are installed):

import { Client } from '@elastic/elasticsearch';
import Mock, { MockPattern } from '@elastic/elasticsearch-mock';

// Setup
const mock = new Mock();
const client = new Client({
    node: 'http://localhost:9200',
    Connection: mock.getConnection(),
});

// Prepare the mock for my "test"
// Use this to track calls (could use something more sophisticated like jest.fn)
const esIndexMockCalls: Array<MockPattern> = [];
mock.add({
    method: ['POST', 'PUT'],
    path: '/:index/_doc/:id',
}, (params: MockPattern) => {
    // Record the call
    esIndexMockCalls.push(params);
    return { status: 'ok' };
});

// Run the "test"
client.index({
    index: 'myindex',
    id: 'mydocument',
    body: {
        foo: 'bar',
    },
}).then(() => {
    // Make "assertions"
    // Check that only one call was made
    if (esIndexMockCalls.length !== 1) {
        throw new Error('Expected one call');
    }
    // Check that we indexed the right thing in the right place
    if (esIndexMockCalls[0].path !== '/myindex/_doc/mydocument') {
        throw new Error('Expected index of mydocument in myindex');
    }
    console.log('Indexed mydocument in myindex as expected');
});

The alternative is to put these assertions in the callback, eg:

mock.add({
    method: ['POST', 'PUT'],
    path: '/:index/_doc/:id',
}, (params: MockPattern) => {
    if (params.path !== 'myindex/_doc/mydocument' {
        throw new Error('Unexpected index path');
    }
    return { status: 'ok' };
});

but this doesn't allow for things like checking how many calls were made. Eg if I have code where I index multiple documents in parallel, I want to be able to check that each one got indexed exactly once.

Is something like this handled by the mock client? Or is using something like jest.fn to track the calls from within the callback the best way of going about this?

Clear mocks

It's very useful to clear mocks completely between different unit tests. Is there any way to do that currently?

Mocking a "scroll" search

Hi,
I really appreciate the mocking library, I've been trying to mock a scroll request... To test it, I want to return the _scroll_id. But, when I include it, I get the error below. Wondering if someone can either point me at an example of how to mock this, or suggest a way forward?

    ResponseError: Response Error

      at Class.<anonymous> (node_modules/@elastic/elasticsearch/lib/Transport.js:257:25)
      at endReadableNT (node_modules/readable-stream/lib/_stream_readable.js:1010:12)

Here's how I'm trying to send the mock in

    esmock.add(
      {
        method: ["GET", "POST"],
        path: "/:index/_search"
      },
      () => {
        return {
          _scroll_id:
            "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAAQWSlpLQUZQbDNTSktXMWxWaDNJcnZZUQ==",
          hits: {
            total: { value: 1, relation: "eq" },
            max_score: 1.0,
            hits: [
              {
                _index: "test",
                _type: "doc",
                _id: "component/f745b673-25fe-5b68-9e8a-361b4e13185a",
                _score: 1.0,
                _source: {
                  id: "component/f745b673-25fe-5b68-9e8a-361b4e13185a"
                }
              }
            ]
          }
        };
      }
    );
    esmock.add(
      {
        method: ["GET", "POST"],
        path: "/_search/scroll"
      },
      () => {
        return {
          _scroll_id:
            "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAAQWSlpLQUZQbDNTSktXMWxWaDNJcnZZUQ==",
          hits: { hits: [] }
        };
      }
    );

ReferenceError: setImmediate is not defined

I am using next.js project with typescript.

I am facing this issue when jest tries to run the mock file

ReferenceError: setImmediate is not defined

      12 | }
      13 |
    > 14 | import { Client } from '@elastic/elasticsearch';
         | ^
      15 | import Mock from '@elastic/elasticsearch-mock';
      16 |
      17 | const mock = new Mock();

      at Object.<anonymous> (node_modules/@elastic/elasticsearch/lib/Helpers.js:27:30)
      at Object.<anonymous> (node_modules/@elastic/elasticsearch/index.js:29:17)
      at Object.<anonymous> (__mocks__/elasticsearch.ts:14:1)

in the file __mocks__/elasticsearch.ts

import { Client } from '@elastic/elasticsearch';
import Mock from '@elastic/elasticsearch-mock';

const mock = new Mock();
const client = new Client({
  node: 'http://localhost:9200',
  Connection: mock.getConnection(),
});

mock.add(
  {
    method: 'GET',
    path: '/',
  },
  () => {
    return { status: 'ok' };
  }
);

client.info(console.log);

How to use elastic-mock properly?

"@elastic/elasticsearch": "^8.1.0",
"@elastic/elasticsearch-mock": "^2.0.0",
"jest": "^24.9.0",
Node: v12.22.7

image

console.log(mock.getConnection()) -> [class MockConnection extends BaseConnection]

console.error node_modules/jest-jasmine2/build/jasmine/Env.js:289
  Unhandled error

console.error node_modules/jest-jasmine2/build/jasmine/Env.js:290
  ResponseError: {"error":"Mock not found"}
      at SniffingTransport.request (.../node_modules/@elastic/transport/src/Transport.ts:532:17)
      at Indices.delete (.../node_modules/@elastic/elasticsearch/src/api/api/indices.ts:301:12)

Fails since 7.14.0

Since updating to @elastic/elasticsearch 7.14.0, I'm getting "error":"Mock not found" in all my unit tests. They worked fine before the update. Has this something to do with client verification in 7.14?

How to seperate mocks from implementation?

I'm trying to use this mock to write unit tests for production code that makes calls to elasticsearch.

I am struggling to see how the actual production code can be seperated from the mock.

For example, how could a test be written for code like this:

async function executeQuery(){
  const client = new Client({
    auth: {
      api_key: ...
    },
  });
  await client.query(...)
}

Test using jest:

test('call execute query with mocked elasticsearch client', async ()=>{
  // what needs to go here to make the function use a mocked client?
  await executeQuery()
})

Having to pass the mock to the Connection parameter of creating the Client requires having the mock in the implementation code that I'm trying to test. Surely it must be possible to have seperate files for the implementation logic and the unit test?

The example shown in the docs shows creating the client and mocking the responses in the same file, but in a real project wouldn't these necessarily be in different places?

How to mock bulk method

Hi, I am trying to mock the bulk method but it is returning the following error:

import { Client } from '@elastic/elasticsearch';
import ElasticMock from '@elastic/elasticsearch-mock';

const esResponse = { took: 9, errors: false };

const mock = new ElasticMock();
const client = new Client({
  node: 'http://localhost:9200',
  Connection: mock.getConnection()
});

mock.add({
  method: 'POST',
  path: '/_bulk'
}, () => esResponse);

let response;
try {
  response = await client.bulk({ body: [{ foo: 'bar' }, { baz: 'fa\nz' }] });
  console.log(response);
} catch (error) {
  console.log(error);
  response = { body: {} };
}
ResponseError: Response Error
          at onBody (/Users/krishankant/Public/Work/fw-bt-3ds-metrics-lambda/node_modules/@elastic/elasticsearch/lib/Transport.js:349:23)
          at Class.onEnd (/Users/krishankant/Public/Work/fw-bt-3ds-metrics-lambda/node_modules/@elastic/elasticsearch/lib/Transport.js:275:11)
          at Class.emit (events.js:223:5)
          at endReadableNT (/Users/krishankant/Public/Work/fw-bt-3ds-metrics-lambda/node_modules/readable-stream/lib/_stream_readable.js:1010:12)
          at processTicksAndRejections (internal/process/task_queues.js:81:21) {
        name: 'ResponseError',
        meta: {
          body: { error: 'Mock not found' },
          statusCode: 404,
          headers: {
            'content-type': 'application/json;utf=8',
            date: '2021-08-16T12:09:51.899Z',
            connection: 'keep-alive',
            'content-length': 26
          },
          meta: {
            context: null,
            request: [Object],
            name: 'elasticsearch-js',
            connection: [Object],
            attempts: 0,
            aborted: false
          }
        }
      }

Can anyone help me to resolve this issue?

how to use with multiple versions ?

Following install multiple versions

I have

"dependencies": {
  "es6": "npm:@elastic/elasticsearch@^6.7.0",
  "es7": "npm:@elastic/elasticsearch@^7.0.0"
}

in my package.json

However when adding elasticsearch-mock, it uses require('@elastic/elasticsearch') for { Connection, errors } (without a peer dependency too ...)

How can I use the client I want with mock ?

How to simulate a delayed response or error?

Hi, I need to simulate a response or error returned by the mocked cluster with a specific time delay. How to do that? By reading the documentation I didn't found any solution.
Thanks

High level API around the http mock

We should not assume that all users know the HTTP style query, we should provide a way to allow them to specify which API to mock.

mock.add({
  api: 'indices.delete'
}, () => {
  return { acknowledged: true }
})

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.