Integration Testing a Restful Endpoint with Request and Jasmine and Node.JS
What is it for?
I am building a simple restful API using Express over MongoDB in order to support a mobile application. Even though the endpoint was quite straightforward, I wanted to ensure it is properly tested. In creating this test, I wanted to fulfill some simple objectives
- Integration style test - completely independent of the implementation
- Setup and Teardown of the database.
- Fast, simple, as little complexity as possible
What Am I Testing?
The API is simple ../rules/clientId/ruleId (get) - Retrieve a specific rule for a specific client ../rules/clientId (get) - Retrieve all of the rules for a specific client ../rules (post) - Store a rule ../rules/clientId/ruleId (delete) - Delete a specific rule for a specific client
Why Use Jasmine? Why not use Frisby.js?
Ah tool choice, such a fraught area. Jasmine is well established and there is a great node port of Jasmine here that is reasonably active. All you Mocha folks and Karma fanatics, pls comment and tell me why framework X is better than Jasmine :)
Frisby.js seems to have a lot of search engine traction but It doesn’t support setup/teardown () and seemed to force me into a certain way of doing things which I didn’t like.
Enough already let’s do it
The full gist is here if you want to look at it…
Setup
To setup, I use beforeEach with the done option because I want to asynchronously move on to the test once the MongoDB call is finished. To setup for the tests, I insert a single ‘rule’ document containing just ID information.
beforeEach(function(done){ MongoClient.connect("mongodb://" + config.mongo_db_address + ":" + config.mongo_db_port + "/" + config.mongo_db_name, function (err, db) { var collection = db.collection('rules'); collection.insert({"clientId": "test","ruleId": "test1"}, function (err, result) { db.close(); done(); }); }); });
Teardown
To teardown, I issue a global delete for all rules belonging to the test client. This is a bit crude but as in all integration style testing, I don’t have the luxury of simply making tests transactional. Note that again, I am using the Async form and using a done method to indicate that things can move on.
afterEach(function(done) { MongoClient.connect("mongodb://" + config.mongo_db_address + ":" + config.mongo_db_port + "/" + config.mongo_db_name, function (err, db) { var collection = db.collection('rules'); collection.remove({ "clientId": "test" }, function (err, result) { db.close(); done(); }); }); });
Update: I am now using uri: not url: and also using the json:true parameter in the request options. this means I don’t need to JSON.parse the bodyString like I was before.. much cleaner.
Testing the Get
Testing the getter method is simple. I simply use the request component to retrieve the body string from the endpoint, use JSON.parse to convert it to an object littoral and then assert its values.
it("should retrieve a specific rule", function(done) { request({ uri: "http://localhost:3000/api/rules/test/test1", json: true }, function(error, response, body){ expect(body.clientId).toEqual("test"); expect(body.ruleId).toEqual("test1"); done(); }); });
Testing the Post
For the Post, I really should check the addition to the DB but I cheat a bit by making a nested get call and verifying the return result (I use the same Kluge for delete).
it("should store a rule", function(done) { request({ method:'POST', uri: "http://localhost:3000/api/rules", json:{"clientId": "test","ruleId": "test2"} }, function(error, response, bodyPost){ request({ url: "http://localhost:3000/api/rules/test/test2", json:true }, function(error, response, body){ expect(body.ruleId).toEqual("test2"); done(); }); }); });
Thats it, hope this helps. I certainly thought this would be easier than it was and had dead ends with Frisby.js (no setup/teardown) and fiddling about with Request but this works. All comments and suggestions accepted.
Michael