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.
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.
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 " ===" }
} }
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"
}
}
}
}
task startMounteBank(type: ExecWait) {
command "mb --loglevel info"
ready "point your browser to http://localhost:2525"
directory "."
}
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
}
task stopMounteBank(type: FreePorts) {
// port: 2525 - mb (MounteBank),
ports = [2525]
}
task runMounteBank(dependsOn: [stopMounteBank, initStubs]) << {
description "Start servers required for development: MounteBank (external API stubs), Redis key/value store"
}
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.
Run MounteBank server log level set to error, so without logging in fact. To make it working some changes in tasks configuration are required.
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 "." }
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.
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 });
}