Skip to content

Instantly share code, notes, and snippets.

@joerx
Last active August 29, 2015 14:27
Show Gist options
  • Save joerx/894f6ca9d6320094806f to your computer and use it in GitHub Desktop.
Save joerx/894f6ca9d6320094806f to your computer and use it in GitHub Desktop.
Generate A Node.js Mock API with PHP

Generate A Mock API with PHP

Why?

Because we can. For the Lulz. Whatever.

Does it work? Where can I download/fork it?

This is a memo, no implementation exists (yet). I might write a proof-of-concept if I ever get to it. Feel free to roll your own and contact me.

General Idea

Use PHP code to define an API and behaviour, then render a JS file from that abstract definition and load it into an express.js app. Control the mock app from inside the PHP test case (start, stop).

Drawbacks:

Probably quite slow. Child process control from PHP is relatively poor. How to ensure forked child get's killed when PHP process dies unexpectedly?

Implementation:

Testcase:

$server = new MockServer();
$server->get('/')->responds(200)
  ->withHeader('Content-type', 'json')
  ->withJson(['message' => OK]);

$server->start();
// do HttpSocket/curl/file_get_contents

$server->kill();

Building an abstract model of the generated app. Mock server, handler, response, etc.:

class MockServer() {
  // Build app model (code in $server->get()):
  public function get($path) {
    $handler = new Handler('/');
    $handler->response = new Response();
    $this->handlers[] = $handler;
    return $handler;
  }
  // similar in $server->post(), etc.

  // Generate the nodejs module containing the mocked API code
  function generateApp() {
    ob_start();
    include('template.php');
    $code = ob_get_contents();
    ob_end_clean();
    file_put_contents('__mock_express_app.js', $code);
  }

}

class MockHandler() {
  function responds($statusCode) {
    $this->response = new Response($statusCode);
    return $this->response();
  }
}

class MockResponse() {
  // Response has ::withHeader(), ::withJson(), etc.
}

App template (template.php):

// require express, etc.
foreach($this->handlers as $handler) {
  echo "app.get({$handler->path}, function(req, res) {";
  echo "res.status({$handler->response->status()})";
  foreach($handler->headers as $header) {
    echo "res.header({$header->name, $header->value})";
  }
  if ($handler->isJson()) {
    echo "res.json(json_encode($handler->json()))";
  } else {
    echo "res.body($handler->body())";
  }
  echo "})";
}

Generated express app in __mock_express_app.js should look something like below. Exports a router that can be mounted into the main app.

var express = require('express');
var _myApp = module.exports = express.router();

// this is the expected result for above mocking code
_myApp.get('/', function(req, res, next) {
  res.status(200);
  res.header('Content-type', 'json');
  res.json({message: 'OK'});
});

Setting up the server. Load the generated module and mount it into an simple express app, start it. Port is injected via env.

var express = require('express');
var mockedApp = require(process.env.APP_FILE);

var app = express();
app.use(bodyParser.json()); // default parser
app.use(morgan()); // for debugging, log requests
app.use(mockedApp); 
app.use(notFound); // default 404 catchall
app.use(errorHandler); // should do something smart to report the error back to the wrapper script

app.listen(process.env.PORT || 12345, function() {
  console.log('MOCK_SERVER_IS_UP');
});

Process Control

Starting and stopping the server process from PHP looks roughly like below. Start node in the background via exec(), use pid to terminate it. Only problem is that the node.js doesn't get killed when the parent PHP process terminates, so it needs to be shutdown explicitly.

// in class Server:

  public function boot() {
    $this->pidFile = "/tmp/{$this->sessId}.pid";
    $this->outFile = "/tmp/{$this->sessId}.out";
    $dir = dirname(__FILE__);

    $cmds = array(
      "cd $dir",
      "npm install > /dev/null 2>&1",
      "PORT={$this->port} node app.js > {$this->outFile} 2>&1 & echo $! > {$this->pidFile}"
    );
    exec(implode(' && ', $cmds));

    // wait until we get the OK from the API server
    for($i = 0; true; $i++) {
      if (preg_match('/MOCK_SERVER_IS_UP/', $this->output())) {
        break;
      } else if ($i > $this->bootTimeOut) {
        throw new Exception('API server timeout');
      } else {
        sleep(1);
      }
    }
  }

  public function kill() {
    posix_kill($this->pid(), SIGKILL);
    unlink($this->pidFile);
    unlink($this->outFile);
  }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment