Skip to content

Instantly share code, notes, and snippets.

@zedar
Last active March 19, 2018 11:19
Show Gist options
  • Save zedar/66a38d9548b7eeb0a491 to your computer and use it in GitHub Desktop.
Save zedar/66a38d9548b7eeb0a491 to your computer and use it in GitHub Desktop.
Starting MounteBank stubs from inside gradle

MounteBank for API stubbing

While developing application with external API calls stubs with some logic are mandatory. MounteBank provides bright and simple solution for stubbing HTTP API.

It is possible to start MounteBank server directly from command line and initialize its content with folllowing curl command:

$ mb
$ curl -X POST -d@./src/test/groovy/online4m/apigateway/si/test/imposter.json http://localhost:2525/imposters

where imposter.json file contains configuration of stubs.

If you use gradle build subsystem it is vital to start mb server directly from build.gradle script.

Configure build.gradle with MounteBank

Running mb server requires two steps:

* starting the *mb* node.js server
* sending the HTTP POST request with imposters configuration - for stubs initialization

Starting the server requires new gradle task for starting shell command and waiting for some response from the server itself.

Add new task definition: ExecWait

Thanks to this great post let's define new task class. There are a few of attributes:

  • command - command to execute

  • ready - string as part of an output from command that if reachable in output stream, should end ExecWait task execution

  • director - directory where command should be executed

  • logOutput - if set to true and ready string is not defined, output from command is logged to the output stream

  • requiredActivePort - if defined on this port some system process should run. If there is no process this task waits up to 6s.

    class ExecWait extends DefaultTask { String command String ready String directory Boolean logOutput = false Integer requiredActivePort

    @TaskAction def spawnProcess() { if (requiredActivePort) { boolean running = false for(int i=0; i < 3; i++) { def cmd = "lsof -Fp -i :$requiredActivePort" def process = cmd.execute() process.in.eachLine { line -> running = true } if (!running) { sleep(2000) } else { break } } } ProcessBuilder builder = new ProcessBuilder(command.split(" ")) builder.redirectErrorStream(true) builder.directory(new File(directory)) Process process = builder.start()

    InputStream stdout = process.getInputStream()
    BufferedReader reader = new BufferedReader(new InputStreamReader(stdout))
    
    if (ready || logOutput) {
      println " === OUTPUT"
      def line 
      while((line = reader.readLine()) != null) {
        println line
        if (ready && line.contains(ready)) {
          println "$command is ready"
          break
        }
      }
      println " ==="
    }
    

    } }

Add new task definition: FreePorts

This task kills processes working on the given port.

class FreePorts extends DefaultTask {
  List ports = []

  @TaskAction
  def freePorts() {
    println " === FreePorts Task started: $ports"
    if (!ports) {
      return
    }
    ports.each { port ->
      def cmd = "lsof -Fp -i :$port"
      def process = cmd.execute()
      process.in.eachLine { line ->
        def killCmd = "kill -9 ${line.substring(1)}"
        def killProcess = killCmd.execute()
        killProcess.waitFor()
        println " === Process killed on port: $port"
      }
    }
  }
}

Define task to start MounteBank server

task startMounteBank(type: ExecWait) {
  command "mb --loglevel info"
  ready "point your browser to http://localhost:2525"
  directory "."
}

Define task to initialize MounteBank imposters

This task requires MounteBank server to be accessible, so depends on the startMounteBank task. It does not required any ready string to check command execution but logs command output.

task initImposters(dependsOn: "startMounteBank", type: ExecWait) {
  command "curl -X POST -d@./src/test/groovy/online4m/apigateway/si/test/imposter.json http://localhost:2525/imposters"
  directory "."
  logOutput true
}

Define task to stop MounteBank server

task stopMounteBank(type: FreePorts) {
  // port: 2525 - mb (MounteBank), 
  ports = [2525]
}

Define task to run MounteBank stubs

task runMounteBank(dependsOn: [stopMounteBank, initStubs]) << {
  description "Start servers required for development: MounteBank (external API stubs), Redis key/value store"
}

Start MounteBank as prerequisite to test task

Before running any test initialize environment, so run server with stubs. When tests are finished clean environment. Shutdown stubs server.

test.dependsOn runMounteBank
test.finalizedBy stopMounteBank

PROBLEM: while load testing application that sends requests to MounteBank, unexpected resource lock could happen

While load testing with, for example, siege command, at least on OSX,

$ siege -b -c 200 -r 100 -H 'Content-Type: application/json' 'http://localhost:5050/api/call POST < ./src/test/groovy/online4m/apigateway/si/test/testdata.json'

unexpected lock/hang on some resources could happend.

After long investigation and debugging it seams that the main reason is logger attached to Console when not MounteBank was not run in terminal mode.

There are two solutions.

Solution #1: if you have to use --production branch

Run MounteBank server log level set to error, so without logging in fact. To make it working some changes in tasks configuration are required.

Change logging level to error for MounteBank

When log level is set to error, MounteBank server does not produce any output. So ready command should be empty task startMounteBank(type: ExecWait) { command "mb --loglevel error" ready "" directory "." }

Change imposters initialization task

Because startMounteBank does not produce ready command so we have to wait for it

task initImposters(dependsOn: "startMounteBank", type: ExecWait) {
  command "curl -X POST -d@./src/test/groovy/online4m/apigateway/si/test/imposter.json http://localhost:2525/imposters"
  directory "."
  logOutput true
  requiredActivePort 2525
}

Now load tests should work normally.

Solution #2: if you can run MounteBank from source code

Apply the following pull request or inside src/mountebank.js file add additional if statement:

if (process.stdout.isTTY) {
  logger.add(logger.transports.Console, { colorize: true, level: options.loglevel });
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment