Testing Spring Boot apps with MochaJS
This article is about testing REST APIs in a Spring Boot application using MochaJS during integration tests phase in Maven.
Why?
This may sound like reinventing the wheel, but believe me - there are some good reasons. I'm working on a SPA web app using REST to communicate with a stateless Spring Boot app, and these are the pros I considered:
- Mocha is really easy - First off, I'm talking about integration test, and not unit tests (where JUnit just shines). Although Spring offers some nice solutions for REST controllers testing, I find Java too verbose to write simple things like posting a JSON to a server URL and test its result. With mocha I write complex tests with just a few lines of code.
- Testing final code ready for deployment - It is common an app to have different behaves during development and production, for example, disable some local caches when in development mode. Spring offers a maven plugin that executes integration tests just after packaging the project, i.e, the code that will be tested is the one that is ready to be deployed.
- Fast coding and testing - Writing integration tests in Spring is not difficult, but it is so time consuming. I'm working on a Spring Boot app that takes from 15 to 20 seconds to start up in my local computer (and it is not a slow one), and every time I want to run a test I must run and wait for the server to start up. Working with Mocha, I run the app once and execute tests as many times I want. Great when you are writing your test scripts.
- The language of the web - This is really important. When you write your REST API, you must have in mind that it might be consumed by a browser, for example. When writing tests in Mocha you will post requests and send results the same way it would be done in a web app, i.e, using JavaScript, which becomes a great source of reference when implementing it a web app.
- A good companion for continuous integration tools - We use Jenkins to build the system on every source code change, and because integration tests are part of Maven building process, we can guarantee that Mocha test scripts will run on every build.
The example
I'll demonstrate a very simple Spring Boot app with a single REST controller to be tested by Mocha. I'm using Maven to build and test the application. I also want Maven to do all the work, like resolve dependency management (both Java and NodeJS), start the application and run Mocha during integration test. The bottom line is, I want Maven to do all that with the command:
$ mvn clean verify
I'm using the verify phase because it is the one that will trigger the integration-test phase inside Maven.
The full source code of this example is available at
https://github.com/rmemoria/spring-boo-mocha-example
I've created an initial Spring Boot app using the generator at
http://start.spring.io/
And I declared a REST controller that exposes a REST API to ping back the given name with the word 'Hello':
@RestController @RequestMapping("/api") public class TestRestController { @RequestMapping(value = "/hello", method = RequestMethod.POST, consumes = { "text/plain" }) @ResponseBody public String sqyHello(@RequestBody String name) { System.out.println(name); return "Hello " + name; } }
If you run the app it can be easily tested with CURL
$ curl --data "world" http://localhost:8080/api/hello -H "Content-Type:text/plain"
And you get
Hello world
Ok, let's create the Mocha script.
I want to have the flexibility to run my Mocha scripts from the command line, so I installed Mocha globally:
$ npm install -g mocha
I also declared a package.json file in the project root. This file is used by NPM to install all dependencies (command npm install) and execute the Mocha test scripts (command npm test).
{ "name": "spring-boo-mocha-example", "version": "1.0.0", "description": "Spring boot app using Mocha for integration tests", "dependencies": { "mocha": "^3.1.2", "supertest": "^2.0.1" }, "devDependencies": {}, "scripts": { "test": "mocha tests/index.js" }, ... }
Supertest is a npm module to test HTTP servers. It will make easier to post HTTP requests to the server and test its result.
The test script is in the tests folder, in the index.js file:
var HOST = 'http://localhost:8080'; var agent = require('supertest').agent(HOST); var assert = require('assert'); describe('Simple API test', function() { it('Hello', function(done) { var req = 'World'; agent.post('/api/hello') .expect(200) .set('Content-Type', 'text/plain') .send(req) .end((err, res) => { assert.equal('Hello ' + req, res.text); done(); }); }); });
It is a very simple test that will make a request to http://localhost:8080/api/hello URL and check its result.
If you want to test your script, you'll have to start the server first
$ mvn clean package $ java -jar target/spring-boot-mocha-example-0.0.1-SNAPSHOT.jar
With the server up and running, just call the Mocha script
$ mocha tests\index.js
And if everything is ok, you may see something like
Simple API test ✓ Hello 1 passing (26ms)
Integrating with Maven
In order to have Maven doing all the work, I'm using 2 extra plugins:
- Frontend-maven-plugin - Download and install Node and NPM locally, and also integrates other Node tools to Maven lifecycle;
- Spring-boot-maven-plugin - provides Spring Boot support in Maven, allowing you to package executable jar or war archives and run an application “in-place”.
So this is how things work when I execute a $ mvn clean verify in the root project folder:
1. Maven will clean up the target folder, because of the clean argument of the mvn command.
2. Maven will install Node and NPM locally in the project root folder, using the frontend-maven-plugin during the generate-resources phase. It will also run the command npm install. It is all achieved with the following declaration in the pom.xml file:
<plugins> ... <plugin> <groupId>com.github.eirslett</groupId> <artifactId>frontend-maven-plugin</artifactId> <version>1.1</version> <configuration> <nodeVersion>v5.6.0</nodeVersion> <npmVersion>3.3.12</npmVersion> </configuration> <executions> <execution> <id>Install node and npm</id> <goals> <goal>install-node-and-npm</goal> </goals> </execution> <execution> <id>npm install</id> <goals> <goal>npm</goal> </goals> <configuration> <arguments>install</arguments> </configuration> </execution>
3. Maven will continue its default lifecycle of compiling and packing the project;
4. Before reaching the verify phase, the spring-boot-maven-plugin will be called to start the application (during the pre-integration-test phase):
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>1.3.5.RELEASE</version> <configuration> <mainClass>com.example.mocha.SpringBootMochaExampleApplication</mainClass> </configuration> <executions> <execution> <id>pre-integration-test</id> <goals> <goal>start</goal> </goals> <configuration> <mainClass>com.example.mocha.SpringBootMochaExampleApplication</mainClass> <maxAttempts>60000</maxAttempts> </configuration> </execution> ...
5. The frontend-maven-plugin will be called again (during integration-test phase) to run Mocha script tests thought NPM:
<plugin> <groupId>com.github.eirslett</groupId> <artifactId>frontend-maven-plugin</artifactId> <version>1.1</version> <configuration> <nodeVersion>v5.6.0</nodeVersion> <npmVersion>3.3.12</npmVersion> </configuration> <executions> ... <execution> <id>integration-test</id> <goals> <goal>npm</goal> </goals> <phase>integration-test</phase> <configuration> <arguments>test</arguments> </configuration> </execution> </executions> </plugin>
6. And finally, during the post-integration-test phase, the application is stopped by the spring-boot-maven-plugin:
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>1.3.5.RELEASE</version> <configuration> <mainClass>com.example.mocha.SpringBootMochaExampleApplication</mainClass> </configuration> <executions> ... <execution> <id>post-integration-test</id> <goals> <goal>stop</goal> </goals> </execution> </executions> </plugin>
In case of error during integration tests with Mocha, Maven interrupts its execution displaying the error produced by Mocha.
Good luck.
Original content: http://rmemoria.blogspot.com.br/2016/11/testing-spring-boot-apps-with-mochajs.html