Related articles: ►Take Me Home – Project Overview ►CI/CD infrastructure: Choosing and setting up a server with Jenkins as Docker image ►Android SDK and emulator in Docker for testing ►Automated Unit- and GUI-Testing for Android in Jenkins
Setting up the testing environment and workflow
Setup:
- Jenkins CI Docker Container
- MongoDB Docker Container
- Production-MongoDB on mlab.com
- NodeJS Web-Application, hosted on heroku.com
Regarding our database tests we wanted to achieve two things: first of all, we wanted to test any functions of our web application using mongoose which change persistent data. Secondly, we needed database tests to test if eventual model-changes are compatible with our data in the production database.
In relational database management systems one defines constraints in the database, which are tested by the database tests. Since we are using MongoDB though, we don’t really have any constraint in the database. Instead we can define any needed constraints with mongoose right in the application. Therefore, cloning the production database for testing is only required when migrating data (or when having constraints defined) but non-essential for the consistency of the as-is backend.
So alternatively when testing a MongoDB we could also use a shell-script or a “before”-function, that gets called once before starting the tests, to define our database testing environment, like creating the same collections we have in the production database and put in test data for our test cases.
Summing up, the workflow is the following:
- Start docker container with our test-MongoDB.
- Configure testing environment: Either copy collections from production database or set it up with a shell-script/before-function.
- When copying data from the production-database, we need to drop all documents in the collections and then fill it with known test data (“known-state” – principle).
- The test-database can be filled with testdata via “before”-methods using mocha.
- Start database test using the configured MongoDB-test-environment.
- Collect results and trigger next step of the Jenkins-CI.
- Stop docker container with our test-MongoDB.
Implementing database-tests with mocha.
Using mocha, we can configure test-cases with the “it”-keyword in a “describe”-block. Each test-case in a “describe”-block will be sequentially called. So even though it would be possible to configure test-cases that build up on each another, like:
- First testcase puts in new data
- Second testcase modifies the new data
- Third testcase deletes the data.
it is a “NoNo”. Why? Imagine the first testcase would fail: all following test cases, that built up on the first one would fail too, and we won’t really have a concrete indication where the error originated from. What we want to do instead, is to kind of reset the database before each test case with a “beforeEach”-function, provided by mocha. It is wise to do this in the “beforeEach”-function to be sure, that a test is using the defined test-data. If we define it in the “afterEach”-function, we cannot be 100% sure that it is called (in case a test crashes).
If we keep this pattern, each test can be triggered independently from other test cases, therefore increasing trackability of the error origination.
We are also using the module “should”, to easily define expected test results.
The following demonstrates the implementation, using pseudo-code to some extent:
var should = require('should') const sampleUser = { username: username, password: password } before(‘set up test-environment’,function(){ //connect to test-database //create collections //done(); }) beforeEach('drop and create test data',function(){ //wipe out all data in collections //create test-data entries in collections }) describe('Model User', function () { it('add user', function (done) { should(sampleUser).have.property('username', username); User.saveUser(sampleUser, (error, user) => { should.not.exist(error); should.exist(user); should(user).have.property('username', username); should(user).have.property('password', password); done() }); }); /* define more testcases */ })
Imagine we somehow don’t have the possibility (or don’t want) to clone the production database into our test-environment and therefore have to set it up ourselves.
In the “before”-function, that is called once before all our test-cases are started, we connect to our test-database in the docker container and create the collections we are also using in the production database. In the “beforeEach”-function we first wipe out any existing data (if something is left over from previous tests) from our collections. Then we create new documents in our collections that we eventually need for our test cases.
In the test case itself, we save the “sampleUser”-object to our database and expect in the callback, that we won’t receive an error and that the fields we wanted to set, are set appropriately.
Conclusion
When testing your non-relational database like MongoDB there is no perfect way that you can use in ALL of your projects, it will always depend on the current circumstances and needs of the project.
Something to keep in mind though:
- Never (or at least very very rarely) run tests against your production database, it might compromise your live-data.
- When working with mongoose, don’t define further constraints in your database. Then you are free to generate collections from your models rather than copying them from the database. Copying is great, but it’s not needed, so always keep in mind what you want to achieve and look for a good balance between cost and reward.
- Known-state-principle: Always organize your test-database to a known-state so that you can eliminate unreproducible side-effects.
With these guidelines helping you out, you are set to run your own database-tests in your next project.
Leave a Reply
You must be logged in to post a comment.