moll / node-mitm

Intercept and mock outgoing Node.js network TCP connections and HTTP requests for testing. Intercepts and gives you a Net.Socket, Http.IncomingMessage and Http.ServerResponse to test and respond with. Super useful when testing code that hits remote servers.
Other
641 stars 48 forks source link

Retrieve request body #77

Closed gr2m closed 2 years ago

gr2m commented 2 years ago

A common use case for http mocking is to match a request by its body. Is there a way to retrieve the request body using today's APIs of Mitm.js?

papandreou commented 2 years ago

Yeah, the req passed as the first parameter to your request handler is an instance of IncomingMessage, so you can consume it as a readable stream and get the request body:

const assert = require('assert');
const createMitm = require('mitm');
const http = require('http');

const mitm = createMitm();

async function consumeReadableStream(readableStream) {
  const chunks = [];
  for await (const chunk of readableStream) {
    chunks.push(chunk);
  }
  return Buffer.concat(chunks);
}

mitm.on('request', async (req, res) => {
  assert(req instanceof http.IncomingMessage);
  const requestBodyBuffer = await consumeReadableStream(req);
  const requestBodyObj = JSON.parse(requestBodyBuffer.toString());
  console.log('Got request body', requestBodyObj);
});

const req = http.request({
  method: 'POST',
  hostname: 'example.com',
  path: '/thepath',
  port: 1234,
  headers: {
    'Content-Type': 'application/json',
  },
});

req.end('{"here": {"you": "go"}}');

Output:

Got request body { here: { you: 'go' } }
gr2m commented 2 years ago

Totally makes sense, thanks a lot!

moll commented 2 years ago

Thanks, @papandreou, for the quick comment! I'll add to that, that you can easily use Mitm.js with Express.js as an example of composability that Mitm.js's approach affords.

That's how I generally test:

var Mitm = require("mitm")
var Router = require("express").Router
var parseBody = require("body-parser").json()

var mitm = Mitm()
var router = Router().use(parseBody)

mitm.on("request", function(req, res) {
    router(req, res, function(err) {
        if (err == null) return
        res.statusCode = 502
        res.end()
    })
})

router.get("/foo/bar", function(req, res) {
    req.headers.host.must.equal("api.example.com")
    req.query.foo.must.equal("bar")
    req.body.must.eql({foo: "bar"})

    res.statusCode = 201
    res.json({foo: "bar"})
})
gr2m commented 2 years ago

This is great, you should add it to the README!