PHPnews.io

Introducing Mocking Hans - An open source tool for creating fake APIs

Written by Nehalist.io / Original link on Jan. 8, 2020

mocking-hans.png

I'm fortunate to work in a company which doesn't just appreciate open source but even contributes to it by releasing some of the tools and libraries we use internally. One of these projects is Hans - a tool for creating fake APIs - which I haven't talked about yet. Well, it's about time!

What is Hans?

Hans is a small Node application for faking APIs - but not just a single REST API using HTTP. It allows for creating multiple APIs using different ports and protocols (like native WebSockets or even GraphQL) while still being easy to use. On top of that it is written entirely in TypeScript which allows for some neat abstractions and sharing models between your real application and your fake API. And, of course, everything is licensed under MIT.

Basic example

APIs are called "Apps" within Hans; apps are just simple TypeScript classes, decorated with the @App decorator (which tells Hans the name and port of the app). Methods within these classes can be decorated with specific decorators to declare endpoints. This might sound confusing, but let's take a look at the example from the documentation:

// apps/Example.ts
import {Get, App, Response} from '@loremipsum/mocking-hans';

@App({
  name: 'example',
  port: 4999,
})
export class Example {
  /**
   * A simple text response
   */
  @Get('/')
  index() {
    return new Response('Hello there!');
  }
}

Starting Hans would now start an HTTP server on port 4999. Opening localhost:4999 would properly simply show "Hello there!".

We could start as many applications this way as we want. Requests for every application are properly logged in the console:

// apps/FirstApp.ts
import {Get, App, Response} from '@loremipsum/mocking-hans';

@App({
  name: 'firstApp',
  port: 42000,
})
export class FirstApp {
  @Get('/')
  index() {
    return new Response('Hello there!');
  }
}

// apps/SecondApp.ts
import {Get, App, Response} from '@loremipsum/mocking-hans';

@App({
  name: 'secondApp',
  port: 43000,
})
export class SecondApp {
  @Get('/')
  index() {
    return new Response('Hello there!');
  }
}
$ npm run hans

> hans-test@1.0.0 hans /srv/dev/node/hans
> hans apps

        ✔️  Started firstApp on localhost:42000
        ✔️  Started secondApp on localhost:43000

Are you ready to ... MOCK?

firstApp (:42000): GET / 200 2.658 ms
secondApp (:43000): GET / 200 0.450 ms
Running two Hans apps, console output after visiting localhost:42000 and localhost:43000
All examples can be found in our GitHub repository!

Adapters

Behind the scenes Hans uses adapters for different protocols. But you don't need to know a lot about them - most things are done behind the scenes.

For example, if you want to create a GraphQL API you simply add the endpoint to your app:

@App({
  name: 'firstApp',
  port: 42000
})
export class FirstApp {  
  @Graphql('/graphql', `
  type Query {
    hello: String
  }
`)
  graphql() {
    return {
      hello: () => 'Hello world!'
    }
  }
}

Hans automatically starts GraphiQL (an in-browser IDE for exploring GraphQL) on localhost:42000/graphql and provides a proper GraphQL endpoint:

Screenshot-from-2020-01-06-13-50-19.png

Requests

The HTTP adapter uses Express for serving a HTTP server, hence we've got access to the request and response object within our endpoints:

import {Post, JsonResponse, App} from '@loremipsum/mocking-hans';
import {Request, Response} from 'express';

@App({
  name: 'firstApp',
  port: 42000
})
export class FirstApp {
  @Post('create')
  create(req: Request/*, res: Response*/) { // Keep in mind that method names are completely up to you!
    return new JsonResponse({ username: req.body.data });
  }
}

Sending a POST request to localhost:42000/create with proper JSON payload now leads to the following result:

Screenshot-from-2020-01-06-14-00-48.png

Responses

Hans helps in handling responses by providing an object-oriented approach to it. Endpoints which return a proper response will be handled directly by Hans, taking away the necessity of setting headers for certain responses yourself. Available responses are:

Middleware

Some APIs might require things like authentication which can easily be implemented using Middleware in Hans using the Middleware decorator. This decorator takes an array of functions and executes all of them before actually executing the endpoint, where all of these functions do have access to the Request and Response object and might even send a response (preventing the actual endpoint from ever being triggered).

In case of authentication we could say we simply check if a proper authorization header is set:

import {Request, Response, NextFunction} from 'express';

export function IsAuthenticated(req: Request, res: Response, next: NextFunction) {
  if (!req.headers.authorization) {
    return res.status(403).json({error: 'You are not logged in!'});
  }
  next();
}

Which then would simply be attached to our endpoint:

  @Get('/authenticated')
  @Middleware([IsAuthenticated])
  authenticated() {
    return new JsonResponse({
      message: 'Hello there'
    });
  }

Simply calling /authenticated now would lead to an error:

$ curl localhost:42000/authenticated
{"error":"You are not logged in!"}

While we pseudo-authenticate - and hence bypassing our middleware - by adding a proper header:

curl -H "authorization: whatever" localhost:4200/authenticated
{"message":"Hello there"}

State

Applications might even share data between them using a global state which is injected in the constructor of every app. Let's assume we've got apps which simply increase a counter every time an endpoint is called:

// apps/FirstApp.ts
import {State, JsonResponse, Get, App} from '@loremipsum/mocking-hans';

@App({
  name: 'firstApp',
  port: 42000
})
export class FirstApp {
  constructor(private state: State) {
  }

  @Get('/')
  index() {
    const counter = this.state.get('counter', 0); // Second argument is the fallback value
    this.state.set('counter', counter + 1);

    return new JsonResponse({
      count: this.state.get('counter')
    });
  }
}

// apps/SecondApp.ts
import {State, JsonResponse, Get, App} from '@loremipsum/mocking-hans';

@App({
  name: 'secondApp',
  port: 43000
})
export class SecondApp {
  constructor(private state: State) {
  }

  @Get('/')
  index() {
    const counter = this.state.get('counter', 0); // Second argument is the fallback value
    this.state.set('counter', counter + 1);

    return new JsonResponse({
      count: this.state.get('counter')
    });
  }
}

Now sending a request to our first app first and then our second app will lead to the following result:

$ curl localhost:42000
{"count":1}

$ curl localhost:43000
{"count":2}

$ curl localhost:42000
{"count":3}

As we can see the counter is increasing which tells us that the store that holds the counter is properly shared.

Why do we need this?

Some time ago we've worked on a product which accumulated the data from multiple APIs and displayed everything in a single UI. Since the folks behind the API were a bit behind their deadlines we had nothing except some API specifications to work with. Waiting a bunch of weeks for them to get their work done was obviously no option - so we went for simply faking their entire API.

The first thing which came into mind was json-server which is another great tool for creating fake APIs. But it lacked two features we really needed:

What started as a necessity soon become something we continously used for our projects and, ultimately, became open source (after we've written some tests and cleaned up some things).

Hans now lives in a lot of our repositories, since it causes very little overhead and allows us to develop on applications without having to rely on real APIs (we even use it for internal UI demos) and it additionally helps a lot when it comes to integration tests.

Why "Mocking Hans"?

3lbkix.jpgI'm so sorry for creating this.

Honestly, I don't even know - but there's a line in the source code which describes it pretty accurate I guess:

// Let's be honest here: I've always wanted to name a class "Hans".

Feedback

Since we'd really happy to get some feedback on a tool we like to work with and think as a useful addition to our set of of tools feel free to give Hans a try and let us know what you think about it on GitHub or in the comments.

nehalist nehalist nehalist nehalist

« 2019 Reflection - The Difficulty of Disclosure, Surebet247 and the Streisand Effect »