Home

Awesome

Mitm.js

NPM version Build status

Mitm.js is a library for Node.js (and Io.js) to intercept and mock outgoing network TCP and HTTP connections. Mitm.js intercepts and gives you a Net.Socket to communicate as if you were the remote server. For HTTP requests it even gives you Http.IncomingMessage and Http.ServerResponse — just like you're used to when writing Node.js servers. Except there's no actual server running, it's all just In-Process Interception™.

Intercepting connections and requests is extremely useful to test and ensure your code does what you expect. Assert on request parameters and send back various responses to your code without ever having to hit the real network. Fast as hell and a lot easier to develop with than external test servers.

Mitm.js works on all Node versions: ancient v0.10, v0.11 and v0.12 versions, previous and current LTS versions like v4 to v12 and the newest v22 and beyond. For all it has automated tests to ensure it will stay that way.

I've developed Mitm.js on a need-to basis for testing Monday Calendar's syncing, so if you find a use-case I haven't come across, please fling me an email, a tweet or create an issue on GitHub.

Tour

Installing

npm install mitm

From v1.0.0 Mitm.js will follow semantic versioning, but until then, breaking changes may appear between minor versions (the middle number).

Using

Require Mitm.js and invoke it as a function to both create an instance of Mitm and enable intercepting:

var Mitm = require("mitm")
var mitm = Mitm()

Mitm.js will then intercept all requests until you disable it:

mitm.disable()

Intercepting in tests

In tests, it's best to use the before and after hooks to enable and disable intercepting for each test case:

beforeEach(function() { this.mitm = Mitm() })
afterEach(function() { this.mitm.disable() })

Intercepting TCP connections

After you've called Mitm(), Mitm.js will intercept and emit connection on itself for each new connection.
The connection event will be given a server side Net.Socket for you to reply with:

mitm.on("connection", function(socket) { socket.write("Hello back!") })

var socket = Net.connect(22, "example.org")
socket.write("Hello!")
socket.setEncoding("utf8")
socket.on("data", console.log) // => "Hello back!"

Intercepting HTTP/HTTPS requests

After you've called Mitm(), Mitm.js will intercept and emit request on itself for each new HTTP or HTTPS request.
The request event will be given a server side Http.IncomingMessage and Http.ServerResponse.

For example, asserting on HTTP requests would look something like this:

mitm.on("request", function(req, res) {
  req.headers.authorization.must.equal("OAuth DEADBEEF")
})

Http.get("http://example.org")

Responding to requests is just as easy and exactly like you're used to from using Node.js HTTP servers (or from libraries like Express.js):

mitm.on("request", function(req, res) {
  res.statusCode = 402
  res.end("Pay up, sugar!")
})

Http.get("http://example.org", function(res) {
  res.statusCode // => 402
  res.setEncoding("utf8")
  res.on("data", console.log) // => "Pay up, sugar!"
})

Please note that HTTPS requests are currently "morphed" into HTTP requests. That's to save us from having to set up certificates and disable their verification. But if you do need to test this, please ping me and we'll see if we can get Mitm.js to support that.

Custom HTTP Methods

Unfortunately because Node.js's web server doesn't seem to support custom HTTP methods (that is, ones beyond require("http").METHODS), Mitm.js doesn't support them out of the box either. The Node.js HTTP parser throws an error given a request with an unsupported method. However, as Mitm.js also supports intercepting at the TCP level, you could hook in your own HTTP parser. I've briefly alluded to it in issue #63.

Bypassing interception

You can bypass connections listening to the connect event on the Mitm instance and then calling bypass on the given socket. To help you do so selectively, connect is given the options object that was given to Net.connect:

mitm.on("connect", function(socket, opts) {
  if (opts.host == "sql.example.org" && opts.port == 5432) socket.bypass()
})

Bypassed connections do not emit connection or request events. They're ignored by Mitm.js.

In most cases you don't need to bypass because by the time you call Mitm in your tests to start intercepting, all of the long-running connections, such as database or cache connections, are already made.

You might need to bypass connections you make to localhost when you're running integration tests against the HTTP server you started in the test process, but still want to intercept some other connections that this request might invoke.
The following should suffice:

mitm.on("connect", function(socket, opts) {
  if (opts.host == "localhost") socket.bypass()
})

Events

All events that Mitm will emit on an instance of itself (see Using Mitm.js for examples):

EventDescription
connectEmitted when a TCP connection is made.<br> Given the client side Net.Socket and options from Net.connect.
connectionEmitted when a TCP connection is made.<br> Given the server side Net.Socket and options from Net.connect.
requestEmitted when a HTTP/HTTPS request is made.<br> Given the server side Http.IncomingMessage and Http.ServerResponse.

License

Mitm.js is released under a Lesser GNU Affero General Public License, which in summary means:

For more convoluted language, see the LICENSE file.

About

Andri Möll typed this and the code.
Monday Calendar supported the engineering work.

If you find Mitm.js needs improving, please don't hesitate to type to me now at andri@dot.ee or create an issue online.