Automated Unit Testing of Node Fn Functions using Jest
By: Lucas Jellema
Tags: Node, Mock, Unit Testing, Fn, 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

Installation of Jest using npm

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.

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.

The function to test is in func.js. The test cases for the function are in func.test.js. The Mock fdk.js replaces the Project Fn FDK during test execution.

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 andImplement 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:

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

const fdk=require('@fnproject/fdk'); fdk.handle(function(input, context){ // adding context parameter let name = 'World'; if (input.name) { name = input.name; } console.log('

Inside Node Hello World function') return {'message': 'Hello ' + name, "ctx":context} // return the context object })

Re-Run the test using

npm test

This should result in an even more positive report: both tests now pass.

Outcome of running “npm test” — executing two Jest test cases

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