Automated Unit Testing of Javascript Fn Functions using Jest image 2

Automated Unit Testing of Javascript Fn Functions using Jest

Crucial pieces of functionality in cloud native applications is implemented in Functions. In the case of Oracle Cloud Infrastructure specifically, the Functions framework is typically Project Fn and the implementation language of choice is … up to the DevOps team. Popular languages for implementing Functions include Go, Python, Java and JavaScript.

Automated code level tests are essential to describe and prove the behavior of the software and to allow rapid and safe refactoring of the code. These tests should be executable in a fully automated way, run against the code as it is without special provisions in the code for running the test, without requiring deployment of the code to a central environment and focus on the core: not the external dependencies used by the code at runtime and not the frameworks that wrap the code at runtime. In the case of Fn Functions, automated code level unit tests should not include the  Fn libraries – but only deal with the code the developers in the DevOps team have written to implement the required functionality in the code.

This article describes what we can do with regard to these tests for Javascript based functions. It shows how we use Jest for writing the test and for running the test and for mocking the FDK (Fn framework). This last step means that we can test the code without executing any part of the Fn libraries.

The steps – that I will demonstrate in detail below are:

  • Prepare environment for Fn (install Fn Server and Fn Client)
  • Install Jest (using NPM)
  • Create Function with JavaScript runtime
  • Configure Jest in function’s package.json
  • Create a Mock FDK
  • Create a Test module func_test.js
    • require the function to test
    • require (mock) fdk
    • optionally: define setup steps to prepare for tests
    • define tests : description, call and expectation
  • Run tests through npm to get a report fo the passed and failed tests
    • no interaction with the FDK libraries takes place when the tests are executed

Demonstration in simplest possible case: Hello World

In an environment set up for Fn and Node development – with Fn Server and Client and with Node and NPM installed – I will demonstrate the installation and configuration of Jest and the creation of the hello function and its corresponding test.

First, create a function hello using the Node runtime:

fn init --runtime node hello

Change to the newly created directory hello and inspect the contents: package.json, func.yaml and func.js – the actual function. For our purpose of testing the function implementation (func.js) we do not really care about func.yaml and how to deploy and invoke the function. So let’s not bother with that.

Install the npm testing module *jest*  (see [jest documentation] for details on how to get started). Jest has rapidly become the industry’s choice for testing JavaScript & Node applications.

Execute this command to install *jest* as a development time dependency:

npm install --save-dev jest

image

Add this snippet to `package.json` to have jest invoked whenever *npm test* is executed – creating a new property at the same level as *main* and *dependencies* :

,"scripts": {

    "test": "jest"

    }

At this point, we can run Jest through npm test. However, there are no tests defined at this point, so this would not be very meaningful.

image

To test *func.js* as well – before the function is deployed to a container- we will use Jest – and in particular the mocking capabilities of Jest. The function – func.js – uses the Fn FDK framework – to handle HTTP requests that are handed to the function for procesing. However, in the test situation, we want to test outside the scope and context of the Fn framework and without any HTTP requests being made.

This can be achieved by using a mock for the fdk module. Jest allows us to define a mock in the following way :

* create file fdk.js in the folder __mocks__/@fnproject under the hello function

* implement the mock version of module @fnproject/fdk with a mock version of function *handle*

* create file func.test.js that uses module @fnproject/fdk and runs tests against *func.js*

The result is shown in the figure. Function hello consists of package.json, func.yaml and func.js and is extended with fdk.js in a mock directory. The code level unit test is defined in func.test.js – named after the the module under scrutiny – requires both fdk.js (and gets the mock implementation) and func.js. It defines the tests against func.js.

When npm test is executed, Jest is ran and it recognizes by name that func.test.js is a unit test it should run, and so it will. This executes the test cases against func.js – without FDK involvement and without deploying the Function.

image

Create the new file for implementing the mock fdk.js:

mkdir /root/hello/__mocks__
mkdir /root/hello/__mocks__/@fnproject
touch /root/hello/__mocks__/@fnproject/fdk.js

Copy this code snippet to the file fdk.js:

const handle = function (f) {
    theFunction = f
    return f
}
let theFunction
const functionCache = function getFunction() {
    return theFunction
}

exports.handle = handle
exports.functionCache = functionCache

When the func.js is required, it invokes function handle on the fdk module and passes a function as input parameter. This function is the actual Fn function implementation that handles the input and context objects to the Fn function. In the case of the mock fdk module, the handle function will simply retain the function reference in local variable theFunction.

The test function invokes functionCache to retrieve the handle to this function and subsequently invoke it – just as it would be in case of the normal invocation of the Fn function.

Create the file func.test.js for the Jest tests for module func:image

Implement func.test.js as follows:

// simply require func.js registers the function (input, context) with mock fdk
const func = require( './func.js' );
const fdk=require('@fnproject/fdk');
const name ="Bob"
const input = {"name":name}
const context = {"_headers":{"Host":"localhost","Content-Type":"application/json"}}
const theFunction = fdk.functionCache() // get the function that was registered in func.js with the (mock) fdk handler
test(`Test of func.js for ${name}`, () => {
  expect(theFunction(input,context).message)
  .toBe(`Hello ${name}`);
})
test(`Test of func.js for Host header in context object`, () => {
  expect(theFunction(input,context).ctx._headers.Host)
  .toBe(`localhost`);
});

 

The story for this test: the test loads the object to test – func.js – and the mock for the Fn FDK. The require of func.js causes the call in func.js to fdk.handle() to take place; this loads the reference to function object defined in func.js in the functionCache. The test gets the reference to the function in the local variable *theFunction*. Two tests are defined:

* when the function is invoked with an object that contains a *name* property, does the response object contain a *message* property that has the specified value?

*  when the function is invoked with a second object – *context* – does the response contain a *ctx* object

Run the test using

npm test

This should report on the tests for function *func*, the real function implementation – but still without the runtime interference of Fn. One test passes, and one fails:

image

To make the failing test pass, a small change is required in func.js:

const fdk=require('@fnproject/fdk');

fdk.handle(function(input, context){
  let name = 'World';
  if (input.name) {
    name = input.name;
  }
  console.log('\nInside Node Hello World function')
  return {'message': 'Hello ' + name, "ctx":context}
})

image

Handson Tutorial

If you want to work with Project Fn and specifically with Javascript functions and adding automated Jest based tests, you can try your hand at this Katacoda scenario in a cloud based tutorial environment: https://katacoda.com/redexpertalliance/courses/oci-course/introduction-fn