Cloud Code Functions

How to Create an Unit Test through their Cloud Code Functions

Introduction

This section will allow you to check the operation and testing of your functions locally using the library parse-server-test-runner.

Prerequisites

To complete this tutorial, you will need:

  • A local environment with Node.js installed to apply unit tests. You can follow the Official NodeJS tutorial to successfully install Node.js at your terminal.
  • An app created at Back4App.
  • Back4App Command Line Configured with the project.

First off, we need to talk about Unit Test!

When developers start writing a function with different intentions in mind, a major point evident in the software community is the application of imaginary scenarios for the created code to be tested. It is necessary to perform the Unit Tests procedure as it allows you to test the code in parts, which ensures that your main code remains intact and uncompromised.

And now, how about a simple practical example?

Let’s assume that you need to write a function to show in a complete sentence, the name from the worker, the position and company. We’ll need to write the function to get the following input items:

  • Company
  • Position
  • Worker name

Steps to complete the simple test

In your terminal, initially, we’ll create a directory and configure your test App (package.json) first, using the following command:

$ mkdir unit-test-sample && cd unit-test-sample
$ npm init

Hint:
Using the npm init command, you’ll be able to create the package.json file. It only covers the most common items and tries to guess sensible defaults. Because of this, we’ll make available the dependencies necessary for the code to work.

The result from “package.json” will be something like the example below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
  "name": "yourfoldername",
  "version": "1.0.0",
  "description": "Just a unit test with using a simple function.",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": ""
  },
  "author": "Your Name",
  "license": "ISC",
  "bugs": {
    "url": "{url}/issues"
  },
  "homepage": ""
}

Now, let’s create the file (index.js) using the command below:

~/unit-test-sample$ touch index.js

We will now insert the code below into the previously created file, and the function will insert an example that can demonstrate this test with the code:

1
2
3
4
5
  // index.js
  module.exports = function(name, position, company) {
  let sentence = "Hi, " + name + "! You are " + position + " in " + company + " company.";
    return sentence;
  };

In NodeJS, the module encapsulates the related code into a single unit of code, and when you’re using module.exports, it increases encapsulated code that can be utilized in other files.

Let’s test 0/

Finally, you can work with your terminal:

~/unit-test-sample$ node
> var moduleBack4App = require('./index.js');
undefined
> moduleBack4App("Jonathan", "Developer", "Back4App")
Hi, Jonathan! You are Developer in Back4App company.

Hint: Using the require() function, you’re able to import and export modules, and in the case above, we are using this function to require a file inside an application.

And using Parse, can I test my function?

Of course, as a test parameter, we will create a backend to control the information of a company’s employees.

Let’s structure the classes as:

Parse.User (Reference for employees)

  • username, email, password (required)

We recommend you to have a detailed look at the Parse Server guide in order to get some more information about User properties.

infoEmployee

  • Position
  • Department
  • WorkShift
  • userId (Pointer)

Step 1 - Understand our final structure

Let’s get started! We will use the Parse Server Javascript Guide as a parameter for the development of our functions. Firstly, after completing the setup using the Command Line Interface (see prereqs), we’ll understand how it will work with the final structure from the files:

├──Back4AppProject
│  ├── cloud
│  │   ├── functions.js
│  │   ├── main.js
│  ├── public
│  ├── package.json
│  ├── index.js
│  ├── node_modules
│  ├── src
│  │   ├── jasmine.js

Notice: When you upload the files to your Cloud Code, the Command Line Interface (See prereqs) will ignore the other files and upload only the ones that are in the public and cloud folder.

Step 2 - Writing our function

After configuring the environment for the Command Line Interface, we’ll write the function to build the proccess to register the Employee and save the additional information. By refactoring the code, at the main.js file, we’ll import these functions into main, like:

1
2
3
4
5
6
7
8
9
10
//In cloud/main.js

var cloudFunctions = require("./functions");

/* It's necessary to insert the Parse Instance in our code,
because at local context not is referenced.*/

Parse.Cloud.define("registerUser",  cloudFunctions.registerUser(Parse));

Parse.Cloud.beforeSave("infoEmployee", infoEmployee.infoEmployee(Parse));

The idea is to decouple the functions from the cloud interface so we may test them without sending HTTP requests inefficiently. This will make a lot of sense as we create the test suite.

cloud-functions.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//In cloud/functions.js

module.exports.registerUser = function(Parse){
  return async(request) =>{
    let params = request.params; //Parameters received  
    let infoEmployee = Parse.Object.extend("infoEmployee"); //Store Information      

    let userCreated = new Parse.User({
      "email" : params.email,
      "username": params.username,
      "password" : params.password
    })

    //Save relation
    try {
      let result = await userCreated.save();

      let information = new infoEmployee({
        "position"   : params.position,
        "department" : params.department,
        "workShift"  : params.shift,
        "user" : result
      });

      return information.save();
    } catch (e) {
        return e.message;
    }
  }
}

module.exports.infoEmployee = function(Parse){
  return async (request) =>{
    let req = request.object;

    if (!req.get("position") || !req.get("department") || !req.get("workShift")) {
      throw new Error("Missing params! The required parameters are: position, department. workShift");
    } else {
      return;
    }
  }
}

cloud-functions.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//In cloud/functions.js

module.exports.registerUser = function(Parse){
  return function (request, response){
    var params = request.params; //Parameters received

    var infoEmployee = Parse.Object.extend("infoEmployee"); //Store Information      

    var userCreated = new Parse.User({
      "email" : params.email,
      "username": params.username,
      "password" : params.password
    })

    //Save relation
    userCreated.save().then((updatedUser) => {
      var information = new infoEmployee({
        "position"   : params.position,
        "department" : params.department,
        "workShift"  : params.shift,
        "user" : updatedUser
      });
      return information.save();
    }).then((info) => response.success(info))
      .catch((e) => {
        response.error(e.message);
      })
  }
}

module.exports.infoEmployee = function(Parse){
  return function (request, response){
    var req = request.object;

    if (!req.get("position") || !req.get("department") || !req.get("workShift")) {
      response.error("Missing params! The required parameters are: position, department. workShift");
    } else {
      response.success();
    }
  }
}

Step 3 - Setting up the environment to test the above code!

For our test suite, we will be using Jasmine, a highly popular JavaScript testing framework. However, our code so far is completely agnostic of our tests, so you may use whatever framework or platform you prefer.

Install the development dependencies

Let’s install Jasmine globally and use, with the commands below:

~/Back4AppProject$ sudo npm install -g jasmine
~/Back4AppProject$ jasmine
Randomized with seed 48094
Started


No specs found
Finished in 0.002 seconds
Incomplete: No specs found

Step 4 - Configuring Parse Server Test Runner in the folder

With our methods implemented in the project Cloud folder in Back4App, we will create new files on our project on Node.js that will configure this interaction.

~/Back4AppProjectsrc$ touch index.js
~/Back4AppProject$ mkdir src && cd src
~/Back4AppProject/src$ touch jasmine.js

Now, we will configure the files created above with the codes shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
  const Promise = require('bluebird');
  const express = require('express');
  const http = require('http');
  const {MongoClient} = require('mongodb');
  const {ParseServer} = require('parse-server');

  const mongoDBRunnerStart = require('mongodb-runner/mocha/before').bind({
    timeout() {
    },
    slow() {
    },
  });
  const mongoDBRunnerStop = require('mongodb-runner/mocha/after');

  const startDB = () => (
    new Promise((done, reject) => {
      done.fail = reject;
      mongoDBRunnerStart(done);
    })
  );

  const stopDB = () => (
    new Promise((done, reject) => {
      done.fail = reject;
      mongoDBRunnerStop(done);
    })
  );

  const connectDB = (databaseURI) => new Promise((resolve, reject) => {
    MongoClient.connect(databaseURI, (err, db) => {
      if (err) {
        reject(err);
      } else {
        resolve(db);
      }
    });
  });

  let parseServerState = {};

  const dropDB = () => {
    const {mongoConnection} = parseServerState;
    return mongoConnection.dropDatabaseAsync();
  };

  /**
    * Starts the ParseServer idropDatabaseAsyncnstance
    * @param {Object} parseServerOptions Used for creating the `ParseServer`
    * @return {Promise} Runner state
   */
  function startParseServer(parseServerOptions = {}) {
    const mongodbPort = process.env.MONGODB_PORT || 27017;
    const {
      databaseName = 'parse-test',
      databaseURI = 'mongodb://localhost:${mongodbPort}/${databaseName}',
      masterKey = 'test',
      javascriptKey = 'test',
      appId = 'test',

      port = 30001,
      mountPath = '/1',
      serverURL = 'http://localhost:${port}${mountPath}',
    } = parseServerOptions;

    return startDB()
      .then(() => connectDB(databaseURI))
      .then((mongoConnection) => {
        parseServerOptions = Object.assign({
          masterKey, javascriptKey, appId,
          serverURL,
          databaseURI,
          silent: process.env.VERBOSE !== '1',
        }, parseServerOptions);
        const app = express();
        const parseServer = new ParseServer(parseServerOptions);

        app.use(mountPath, parseServer);

        const httpServer = http.createServer(app);

        Promise.promisifyAll(httpServer);
        Promise.promisifyAll(mongoConnection);

        return httpServer.listenAsync(port)
          .then(() => Object.assign(parseServerState, {
            parseServer,
            httpServer,
            mongoConnection,
            expressApp: app,
            parseServerOptions,
          }));
      });
  }

/**
 * Stops the ParseServer instance
 * @return {Promise}
 */
  function stopParseServer() {
    const {httpServer} = parseServerState;
    return httpServer.closeAsync()
      .then(stopDB)
      .then(() => parseServerState = {});
  }

  module.exports = {
    dropDB,
    startParseServer,
    stopParseServer,
    parseServerState,
  };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const { startParseServer, stopParseServer, dropDB } = require('parse-server-test-runner');

describe('registerUser', () => {
   beforeAll((done) => {
    const appId = 'test';
    const masterKey = 'test';
    const javascriptKey = 'test';

    startParseServer({ appId, masterKey, javascriptKey })
      .then(() => {
        Parse.initialize(appId, masterKey, javascriptKey);
        Parse.serverURL = 'http://localhost:30001/1';
      })
      .then(done).catch(done.fail);
  }, 300 * 60 * 2);

  afterAll((done) => {
    stopParseServer()
      .then(done).catch(done.fail);
  });

  beforeEach((done) => {
    dropDB()
      .then(done).catch(done.fail);
  });

  it('should work', (done) => {
    const q = new Parse.Query('_User')
    q.limit(5)
      .find({ useMasterKey: true })
      .then(console.log)
      .then(done).catch(done.fail);
  });
});

The last step is to configure the package.json, using the command: $ npm init in the root directory (the file below is just an example with the modules required):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
  "name": "back4approject",
  "version": "1.0.0",
  "description": "Back4App guide using for reference the Parse Server Test Runner",
  "main": "index.js",
  "engines": {
    "node": ">=6"
  },
  "repository": {
    "type": "",
    "url": ""
  },
  "keywords": [
    "parse",
    "parse-server",
    "testing",
    "tests"
  ],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bluebird": "^3.5.0",
    "express": "latest",
    "mongodb": "^2.2.30",
    "mongodb-runner": "^3.5.0",
    "parse": "^1.10.0",
    "parse-server": "^2.5.3",
    "parse-server-test-runner": "^1.0.0"
  }
}

And now, you’ll check that we approach the structure described in these previous steps :)

Step 5 - Returning to the terminal

We’ll start to configure the local testing, for this we’ll follow the command below to the code for set up programmatically for testing purposes.

~/Back4AppProject$ # in the same directory from package.json
~/Back4AppProject$ npm install

After the successful installation, you’re able to check your unit test locally with the command described and receive the result, such as:

~/Back4AppProject$ jasmine src/jasmine.js
Randomized with seed 79055
Started
✔  Downloaded MongoDB 3.6.5
[]
.


1 spec, 0 failures
Finished in 19.983 seconds
Randomized with seed 79055 (jasmine --random=true --seed=79055)


Wonderful, it’s ready!

With the guide described above, you’re able to work with the Parse Server Test Runner and your functions developed for the Cloud Code to Back4App.