Giter Site home page Giter Site logo

trayio / mock-inspect Goto Github PK

View Code? Open in Web Editor NEW
18.0 23.0 1.0 865 KB

Mocks network requests and allows you to make assertions about how these requests happened. Supports auto-mocking of graphQL requests given a valid schema.

License: MIT License

JavaScript 5.01% Shell 0.88% TypeScript 94.11%
mocking mocking-framework mocking-library mocks testing testing-tools api-mock graphql graphql-mock

mock-inspect's Introduction

mock-inspect

Statement coverage Function coverage Line coverage Branches badge Mutation badge

Mocks network requests and allows you to make assertions about how these requests happened. Supports auto-mocking of graphQL requests given a valid schema.

An example using jest:

// Let's imagine we are testing an application in which the user has to login.
// We set up a mock for the /login endpoint to not use the real API in our test.
const mock = mockRequest({
    requestPattern: "https://www.yourwebsite.com/login",
    requestMethod: "POST",
    responseStatus: 201,
    responseBody: "Welcome!"
})
// Once the mock is set up, we will execute the application code in our test
// which makes a request to the /login endpoint - the response will be mocked.
// ... Execute your application code which would perform the login ...
// After the request has been executed, we can see how our application made the
// request. For our login scenario, we could check that the application
// forwards the username and password in the correct format as POST payload.
const requestProps = mock.inspect()
expect(requestProps.requestBody).toEqual({username: "Han", password: "Ch3w!3"})

Table of Contents

  1. Installation
  2. Setting up your test suite
  3. Available functions and classes
  4. Using GraphQL
  5. Unresolved promises in tests, i.e. React tests with jest
  6. Development

Installation

Installing mock-inspect is simple: Fetch it from the npm registry using your favourite package manager, either npm or yarn.

# with npm
npm install --save-dev mock-inspect
# with yarn
yarn add -D mock-inspect

Setting up your test suite

Our mocking solution has to be set up and torn down accordingly after tests. The method cleanUpNetworkRequestMocking should run before each of your tests, setUpNetworkRequestMocking() once before all of your tests, and tearDownNetworkRequestMocking once after all of your tests. How setup steps have to be invoked depends on your test runner. With jest, we would create a setupTestFrameworkFile which would register the following hooks:

const {
    cleanUpNetworkRequestMocking,
    setUpNetworkRequestMocking,
    tearDownNetworkRequestMocking
} = require("mock-inspect")

beforeEach(() => {
    cleanUpNetworkRequestMocking()
})

beforeAll(() => {
    setUpNetworkRequestMocking()
})

afterAll(() => {
    tearDownNetworkRequestMocking()
})

Persistent auto-generated graphQL responses in the setup phase

For graphQL APIs, we expose functionality in the setup phase to automatically generate graphQL responses from a schema throughout your entire test suite. (These can be overridden if necessary though). The below example sets up the test suite in such a way so that any graphQL request going against https://thisdomaindoesnotexist.com/graphql will be evaluated against the provided graphQLSchema and an automatic response will be generated. Refer to the type definitions for all available options.

beforeAll(() => {
    setUpNetworkRequestMocking({
        persistentAutoGeneratedGraphQLMocks: [
            {
                requestPattern: "https://thisdomaindoesnotexist.com/graphql",
                graphQLSchema: schemaString,
            },
        ]
    })
})

Enable unmocked network requests to pass the network

By default, network requests that aren't expected to be mocked will throw an error saying that a response handler hasn't been set up for this request. You can disable this behaviour by passing the option blockUnmockedNetworkRequests: true into the setUpNetworkRequestMocking method.

    setUpNetworkRequestMocking({
        allowUnmockedRequestsOnNetwork: true
    })

Available functions and classes

Please find below a list of available functions and class methods. For detailed examples on how to use each of these, check out our extensive suite of tests. The types for the possible option objects have been thoroughly annotated - make use of your IDE hints to see the full details for each property.

mockRequest

Mocks a request from scratch using the details you provide.

Receives an object which defines the properties of the request to be mocked and the response to be returned. Check out the type definition for details of properties you can enter.

Returns an instance of the MockedRequest object. You can call available methods from this object to inspect the request.

When creating multiple mocks for the same URL, we will always use the response details of the last call to mockRequest.

const {mockRequest} = require("mock-inspect")
// Set up mock:
const mock = mockRequest({
    requestPattern: "https://www.yourwebsite.com/login",
    requestMethod: "POST",
    responseStatus: 201,
    responseBody: "Welcome!",
    responseHeaders: {
        "Authorization": "take your token good sir!"
    }
})
// You can now use all available methods on the MockedRequest class, such as
// checking that the request has been made or inspecting which properties have
// been used to make it:
mock.expectRequestToHaveBeenMade()
const requestProps = mock.inspect()
// You can use the requestProps object to make assertions how the request was
// made. See 'inspect' in this documentation for more information.

Using mockRequest for graphQL

Pass the property graphQLMutationName or graphQLQueryName which should align with the query or mutation you are mocking. For this to work, the graphQL requests by your application have to be made with a JSON payload (content-type: application/json header) that includes the query property.

const firstMockedRequest = mockRequest({
    graphQLMutationName: "FirstMutation",
    responseBody: "Was set up first",
    requestMethod: "POST",
})
const secondMockedRequest = mockRequest({
    graphQLQueryName: "SecondQuery",
    responseBody: "Was set up second",
    requestMethod: "POST",
})

// Receives the mocked response "Was set up second" although was called first
await request(`query SecondQuery { animals { cats } }`)
// Receives the mocked response "Was set up first" although was called second
await request(`mutation FirstMutation(call: "Meow") { id }`)

MockedRequest

Every time you mock a request, you get hold of this class which has the following methods:

inspect

Returns an object with information about how the network request has been made, using the properties requestBody and requestHeaders. Check out the type definition for details of the returned properties.

If the request has not been made yet on time of calling .inspect(), an error message will be thrown.

You can use the request information object in any way you like - you could check for equality, test whether the schema matches (i.e. using jest-json-schema-extended) and many more!

// Set up mock:
const mock = mockRequest({
    requestPattern: "https://www.yourwebsite.com/login",
    requestMethod: "POST",
    responseStatus: 201,
    responseBody: "Welcome!",
    responseHeaders: {
        "Authorization": "take your token good sir!"
    }
})
// ... Execute in your test application code which should make the request ...
// Use `inspect()` to retrieve information about how the request has been made.
// In the example below, we would use jest's expect method that the request body
// included the correct properties and that JSON format was specified in the
// request headers. You don't have to use jest's expect though - you can use
// the returned object of request information in any way you like!
const requestProps = mock.inspect()
expect(requestProps.requestBody).toEqual({username: "Han", password: "Ch3w!3"})
expect(requestProps.requestHeaders["content-type"]).toEqual("application/json")

expectRequestToHaveBeenMade

Asserts that the network request you mocked has been called.

const loginRequest = mockRequest({/* mock details go here */})
loginRequest.expectRequestToHaveBeenMade()

expectRequestToNotHaveBeenMade

Asserts that the network request you mocked was not called.

const loginRequest = mockRequest({/* mock details go here */})
loginRequest.expectRequestToNotHaveBeenMade()

Using GraphQL

We also support GraphQL - both for creating mocks and asserting on these mocks.

Mocking graphQL calls by query or mutation name

As outlined in the section about mockRequest, you have to provide an additional property to pass in the query or mutation name you are mocking so that we know you are mocking a graphQL request.

Auto-generating graphQL responses

If desired, we can automatically generate a random graphQL response for you. That way, you don't have to manually define the responseBody property. To do so, you need to provide us with the graphQL schema of the request that will be made, in the property graphQLAutoMocking.schema. (When graphQLAutoMocking.schema and responseBody is given, we will always use the responseBody property.)

Consider the following example:

// Note that `responseBody` is not given
const mockedGQLRequestWithSchema = mockRequest({
    requestPattern: /\/graphql/i,
    requestMethod: "POST",
    graphQLQueryName: "MyCoolQuery",
    graphQLAutoMocking: {
        // `schema` accepts a graphQL schema as string. An example:
        // https://github.com/trayio/mock-inspect/blob/main/src/tests/fixtures/simpleGraphQLSchemaString.ts
        schema: simpleGraphQLExampleSchemaString,
    },
})
// The response to this request will be automatically generated. It will look like:
// { me: { name: "Drucill Hubert Lewse the Comer of Avera rejoices Fiann Craggy Florie and 5 hard trouts" } }
const response = await exampleGraphQLPostRequestJson(`
    query MyCoolQuery {
        me {
            name
        }
    }
`)

Setting a fixed length or range for auto-generated array responses

The property graphQLAutoMocking.fixedArrayLengths can be used to set a fixed array length or range for arrays that will be auto-generated. For this to work, we need to know of the type and the property in the format type.property and the desired length/range.

Consider the below example: We are mocking a graphQL request that is expected to return a users array. As we can see on the schema, the users property sits underneath type Query. If we desire the users array to be of length 3, we would pass "Query.users": 3. If we desire it to be between the lengths 3 and 7, we would pass "Query.users": [3, 7].

const mockedGQLRequestWithSchema = mockRequest({
    requestPattern: /\/graphql/i,
    requestMethod: "POST",
    graphQLQueryName: "MyCoolQuery",
    graphQLAutoMocking: {
        schema: simpleGraphQLExampleSchemaString,
        fixedArrayLengths: {
            "Query.users": [3, 7],
        },
    },
})
// The property `users` in this response will be of length between 3 and 7
await exampleGraphQLPostRequestJson(`
    query MyCoolQuery {
        users {
            id,
            email,
        }
    }
`)

Unresolved promises in tests, i.e. React tests with jest

Currently, jest provides no API to flush promises on its own. But flushing promises is necessary to have jest execute all built-up promises - and network responses are also promises. Therefore, you must flush the built-up promises manually. For this use case, we recommend using the async utility waitFor which is provided by the "Testing Library" project. Alternatively, you could create your own function which flushes a promise and call it as many times as needed:

export const flushPromises = async () => {
    await new Promise(setImmediate)
}
// Call `flushPromises` as many times as needed
await flushPromises()

Development

This library is based on the msw library.


Chameleon graphic by Анна Куликова from Pixabay

mock-inspect's People

Contributors

dependabot[bot] avatar rickschubert avatar thomaschaplin avatar trayprod avatar

Stargazers

 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Forkers

aychtang

mock-inspect's Issues

Feature request: expect how many times a request has been made

Currently, we can only check IF a request has been made using expectRequestToHaveBeenMade and expectRequestNotToHaveBeenMade. From internal statistics at trayio, these two methods are by far the most popular ones in the library.

Extending on this I think it would be nice to be able to expect how often a request was made. I suggest the following API:

const loginRequest = mockRequest({
    requestPattern: "https://www.example.com/login",
    requestMethod: "POST",
    responseBody: "hereGoesMyAuthToken",
    persistent: true
})
loginRequest.expectRequestToHaveBeenMadeTimes(8)

The particularity of this is that this would currently only work if persistent: true is used. Otherwise, the mock response will only take effect one single time when the request is made.

Remove unnecessary stacktrace clipping

We used to have a feature where we clip the stacktraces so that our users always know which of their tests throws the error; we didn't want the stacktrace to point to mock-inspect internals. This was necessary in the past when we were still doing data evaluation inside mock-inspect.

As per previous pull requests, we have gotten rid of this internal data evaluation though and instead went for a model where the caller themself just gets the data about how the network request works and they can do whatever they want with it. Thus, we should remove this complicated stacktrace clipping and passing around.

Allow users to create their own assertions based on how request was made (Previously: "Add method which checks how a request has been made with a schema")

Currently, there is a method expectRequestMadeMatching which performs an exact match comparison to check whether the network request has been made with the supplied parameters.

I would find it useful to have another method called expectRequestMadeMatchingSchema. This method would work the same way as expectRequestMadeMatching but users would be able to pass in JSON schemas which are then being validated.

This new feature would help in cases where parameters are being auto-generated by the application code, i.e. when it always passes a parameter id which is then a random uuid.

Using Promise.all() with mock-inspect to the same endpoint - response is always the same

Below is a question which I felt suitable to be added to the issues so we can track this and keep a log of the response in case anyone comes across the same question.

Question:

So, I've got some code that makes a number of api calls to the same endpoint with different request bodies concurrently in a Promise.all(). I'm writing tests for this code, mocking the responses with mock-inspect . What I found is that one mockRequest({...}) mocks all of the calls made within the Promise.all and the mocked response is the same for all calls. Is this expected behaviour? I would have assumed it to be possible to mock each of the api calls separately

Improve examples

The following points have been raised when the tool was being presented:

  • The examples could be more accessible

Make graphQlQueryName __or__ graphQlMutationName relationship clear using typing

When calling mockRequest, users are not allowed to add graphQlQueryName AND graphQlMutationName at the same time. If the user does so, we will throw an error at runtime telling the user that the creation of this mock is incorrect.

The above behaviour is useful and I would leave it in. It makes a lot of sense for when developers create code without IDE hints. But I propose to add an additional way of warning the user: We could type the MockResponseOptions interface so that it will tell the user already at compile time/at time of writing code in their IDE (i.e. VSCode) that one can't define the two properties at the same time.

Expose stryker mutation testing score/report

It would be nice to present the mutation testing report and host it on Github pages to improve visibility of test coverage. This will also make it easier for contributors to add to the test coverage.

Suggested improvement to use function args with req / res

The following points have been raised when the tool was being presented:

The tool could use req and res parameters as a function "as this is the standard for libraries like these". While the nock and msw libraries indeed use such a pattern, I would actually push back on this one as the current API with its object structure has been in discussions and design phases since this tool's inception exactly because our impression was that function parameters are too clunky.

Move "Available functions" down in readme

Now that we have an example at the very top of the readme, it is not necessary anymore to have the available methods at the top of the readme. (We used to do this in order to put high emphasis on how mock-inspect can help your tests). It would make logically more sense if the setup steps are placed after the installation notes.

Feedback gathered from colleagues

The following points have been raised when the tool was being presented:

  • one method uses the word "Network", the other one doesn't - the names expectRequestMadeMatching and expectNetworkRequestToHaveBeenMade are inconsistent

Move contract testing into it's own readme section

Currently, the expectRequestMadeMatchingContract method in the readme is not clear that this should be used as something along the lines of a global store.

We need to explain how this should be used with clearer examples.

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.