mockttp

repository·main·Indexed 21 days ago

https://github.com/httptoolkit/mockttp

A mock HTTP server for testing HTTP clients and stubbing webservices in JavaScript. It supports high-fidelity integration testing in Node.js and browser environments, featuring HTTPS, transparent proxying, and parallel test execution via dynamic ports. The library provides three setup modes: getLocal() for in-process servers, getRemote() for remote configuration, and getAdminServer() for managing multiple mock sessions.

Tokens
19.5K
Snippets
60
Records
86
Agent score
72%

What's inside mockttp

  1. Understand Mockttp setup modes: getLocal, getRemote, and getAdminServer

    main

    Mockttp provides three primary ways to instantiate a client, depending on your environment and whether the mock server is running in-process or remotely:

    • getLocal(): Returns a Mockttp instance using a local in-process mock server. In Node.js, this automatically manages server lifecycle. In a browser, this is an alias for getRemote().
    • getRemote(): Returns a Mockttp instance that communicates with a separate admin server to configure a remote mock server. This is required for browser-based testing (since browsers cannot host HTTP servers) and is useful for testing mobile/IoT devices on a network.
    • getAdminServer(): Returns an admin server (Node.js only) that allows remote clients to start, stop, and configure multiple mock servers. You can run this via the CLI helper mockttp -c [test command] to automate lifecycle management.
    const mockServer = require('mockttp').getLocal();
    const adminServer = require('mockttp').getAdminServer();
  2. Set up Mockttp for Browser testing

    main

    Browser testing requires an external Admin Server because browsers cannot host HTTP servers.

    1. Start an Admin Server: Use the CLI mockttp -c [test command] or start it manually in Node using getAdminServer().
    2. Connect from the Browser: Use getLocal() inside your browser code to connect to the admin server.
    3. Direct Traffic: Either point your application to mockServer.url or start the mock server on a fixed port (e.g., mockServer.start(8080)) and configure the browser to use that port as a proxy.
    // 1. Start admin server in Node
    const adminServer = require('mockttp').getAdminServer();
    adminServer.start().then(() => runTests()).finally(() => adminServer.stop());
    
    // 2. Connect in the Browser
    const mockServer = require('mockttp').getLocal();
    mockServer.start();
  3. Run an HTTP integration test with Mockttp

    main

    To perform an HTTP integration test, follow these steps:

    1. Start a Mockttp server using mockttp.getLocal().
    2. Define mocks for specific endpoints using .forMethod(path).thenReply(...).
    3. Execute real HTTP requests against the server or through the proxy.
    4. Assert on the responses or inspect the requests received by the server.

    This workflow works in both Node.js and modern browsers.

    const superagent = require("superagent");
    const mockServer = require("mockttp").getLocal();
    
    describe("Mockttp", () => {
        // Start your mock server
        beforeEach(() => mockServer.start(8080));
        afterEach(() => mockServer.stop());
    
        it("lets you mock requests, and assert on the results", async () => {
            // Mock your endpoints
            await mockServer.forGet("/mocked-path").thenReply(200, "A mocked response");
    
            // Make a request
            const response = await superagent.get("http://localhost:8080/mocked-path");
    
            // Assert on the results
            expect(response.text).to.equal("A mocked response");
        });
    });
  4. Set up Mockttp in Node.js

    main

    For Node.js environments, use getLocal() to run the mock server in-process. You must manually call .start() before tests and .stop() after tests. To direct traffic to the mock server, you can either point your application to mockServer.url or use the provided mockServer.proxyEnv to configure proxy settings via environment variables.

    const mockServer = require('mockttp').getLocal();
    
    // Before each test, start up the server:
    mockServer.start();
    
    // After each test, stop the server:
    mockServer.stop();
    
    // To use as a proxy via environment variables:
    process.env = Object.assign(process.env, mockServer.proxyEnv);
  5. Configure HTTPS mocking in Mockttp

    main

    To mock HTTPS, you must generate a CA certificate, provide it to Mockttp, and ensure your HTTP client trusts it.

    1. Generate a certificate using OpenSSL:

    openssl req -x509 -new -nodes -keyout testCA.key -sha256 -days 365 -out testCA.pem -subj '/CN=Mockttp Testing CA - DO NOT TRUST'

    2. Pass the certificate to Mockttp during setup using the https option:

    const mockServer = getLocal({
        https: {
            keyPath: './testCA.key',
            certPath: './testCA.pem'
        }
    });

    3. Trust the certificate in your client:

    • Node.js: Set the NODE_EXTRA_CA_CERTS environment variable to the path of your .pem certificate.
    • Chrome: Use the --ignore-certificate-errors-spki-list=<spki fingerprint> flag. To get the fingerprint, run: openssl x509 -in testCA.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
    • Firefox: Manually add the certificate as a CA in a dedicated test profile.
  6. Understand Mockttp request and response terminology

    main

    Mockttp uses specific terminology to describe network targets and connection details:

    • Hostname: A string representing an IP address or domain name.
    • Host: A string containing the hostname plus an optional port (if not the default for the protocol).
    • Destination: A structured object containing a mandatory hostname (string) and port (number).
    • Remote IP/Port: The IP address and port of the client that initiated the connection.
  7. How to use RequestRuleBuilder to define mock rules

    main

    The RequestRuleBuilder is a fluent API used to define how Mockttp should intercept and handle specific HTTP requests.

    Workflow:

    1. Initiate: Start a builder from a Mockttp instance using methods like .forGet(path), .forPost(path), or similar. This defines the initial matching criteria (Method and Path).
    2. Configure: Chain methods to add more precise matching behavior, add delays, or register webhooks.
    3. Handle: Use terminal methods (starting with then...) to define the response or action (e.g., thenReply, thenJson, thenCallback).
    4. Register: The terminal methods return a Promise<MockedEndpoint>. You must await this promise to ensure the rule is fully registered with the server before sending requests to it. The returned MockedEndpoint can be used to assert on the requests that match the rule.

    Note: You should never instantiate RequestRuleBuilder directly; always use the Mockttp instance.

    // Example workflow
    const endpoint = await mockttpInstance
      .forGet('/api/user')
      .delay(100)
      .thenJson(200, { id: 1, name: 'John Doe' });
    
    // Now the rule is active and 'endpoint' can be used for assertions
  8. Use ProxySettingSource for dynamic proxy selection

    main

    A ProxySettingSource allows you to determine the proxy configuration dynamically. It can be one of the following:

    1. A fixed ProxySetting object.
    2. A ProxySettingCallback: A function that receives ProxySettingCallbackParams (containing the hostname) and returns a ProxySetting or undefined.
    3. An Array<ProxySettingSource>: An array of sources that are evaluated in order. The first source that returns a valid ProxySetting is used.
    4. undefined.
    type ProxySettingCallbackParams = { hostname: string };
    type ProxySettingCallback = (params: ProxySettingCallbackParams) => Promise<ProxySetting | undefined> | ProxySetting | undefined;
    
    // Example of a dynamic callback
    const dynamicSource: ProxySettingCallback = async ({ hostname }) => {
      if (hostname.endsWith('.internal.com')) {
        return { proxyUrl: 'http://internal-proxy:8080' };
      }
      return undefined;
    };
  9. Use RequestStepDefinition to build mock request sequences

    main

    Mockttp allows you to define a sequence of actions (steps) that occur when a request matches a rule. These steps are implemented via classes that implement the RequestStepDefinition interface. Steps can be terminal (ending the request lifecycle) or non-terminal (allowing subsequent steps to execute).

    Common step types include:

    • Terminal Steps: CloseConnectionStep, ResetConnectionStep, TimeoutStep, and JsonRpcResponseStep (when providing a final result/error).
    • Non-Terminal Steps: DelayStep, InformationalResponseStep, WaitForRequestBodyStep, and WebhookStep.

    You can use the StepDefinitionLookup to identify available step types by their string identifiers.

    import { StepDefinitionLookup } from 'mockttp';
    
    // Example of the types of steps available via lookup
    // 'simple', 'callback', 'stream', 'file', 'passthrough', 'close-connection', 
    // 'reset-connection', 'timeout', 'json-rpc-response', 'delay', 
    // 'wait-for-request-body', 'webhook', 'informational-response'
  10. How Certificate Transparency (CT) works in Mockttp

    main

    When creating a CA, you can enable certificateTransparency: true in the options. This allows Mockttp to simulate Certificate Transparency by:

    1. Deterministically deriving two CT log operators from the CA certificate.
    2. Embedding Signed Certificate Timestamps (SCTs) into the generated leaf certificates.

    To inspect the logs used by your CA, use getCTLogDetails() on the CA instance. To derive logs from a raw certificate string, use getCertificateTransparencyLogs(caCert).

    // 1. Enable CT when creating the CA
    const ca = await getCA({
        certificateTransparency: true
    });
    
    // 2. Retrieve the log details
    const logs = ca.getCTLogDetails();
    // logs: Array<{ logId: Buffer, publicKey: Buffer }>
    
    // 3. Generate a certificate (it will now contain SCTs)
    const leaf = await ca.generateCertificate('example.com');
  11. How to build WebSocket interception rules

    main

    WebSocket rules are built using a fluent API starting from a Mockttp instance. You typically begin by calling .forAnyWebSocket(path) to create a WebSocketRuleBuilder.

    Once you have the builder, you can chain configuration methods like .delay(ms) to add latency. To finalize and register the rule, you must call one of the .thenX() methods (e.g., .thenEcho(), .thenPassThrough()).

    Important: The .thenX() methods return a Promise<MockedEndpoint>. Because rule registration can be asynchronous (especially when using a remote server or browser testing), you should await this promise to ensure the rule is active before sending requests that should match it.

    // Example workflow
    const endpoint = await mockttp.forAnyWebSocket('/ws')
        .delay(100)
        .thenEcho();
    
    // The rule is now active and will echo messages on /ws after a 100ms delay.