Learn JS by building a UI framework: Part 2 - Testing
Tags: testing, javascript, node, recursion
Published: 2019-06-14 18:08:49

---
title: Learn JS by building a UI framework: Part 2 - Testing
published: true
description: A first principles approach to learning JavaScript 
tags: testing, javascript, nodejs, recursion
---

*This article is the second in a series of deep dives into JavaScript. You van view the first article [here](https://dev.to/carlmungazi/learning-javascript-by-building-a-ui-framework-from-scratch-1767).* 

*This series does not comprehensively cover every JavaScript language feature. Instead, features are covered as they crop up in solutions to problems. Also, every post is based on tutorials and open source libraries produced by other developers, so like you, I too am also learning new things with each article.*

___ 


# Let the testing begin

To test or not to test, that is the question. At the end of the last post we pondered our next move after writing the first piece of code for our framework. We surmised that we could either write more code until we have a working framework or begin adding tests. Each option has tradeoffs. Writing more code means quickly creating a working prototype. Alternatively, writing tests means more robust code early on but at the price of slower development. There is no right or wrong answer. Sometimes you need to rapidly prototype and test different solutions whilst other times you add tests so you write better code. 

We will take the testing route because in addition to making our code more robust, it gives us another chance to explore JavaScript from a different angle. Below is a reminder of what our `createElement` function currently looks like:

```js
function createElement (type, opts) {
  if (type == null || typeof type !== 'string') {
    throw Error('The element type must be a string');
  }

  if (arguments[1] !== undefined && Object.prototype.toString.call(opts) !== '[object Object]') { 
    throw Error('The options argument must be an object'); 
  }

  const { attrs = {}, children = [] } = opts || {};

  return {
    type,
    attrs,
    children
  }
}
```

And below are the tests we will cover in this article.

```js
group('createElement function', () => {
  check('it creates a virtual dom object', () => {
    const target = createElement('div', {});
    const copy = { type: 'div', attrs: {}, children: [] };
    assert.isDeeplyEqual(target, copy);
  }); 

  check('it throws errors when a string is not specified as the first argument', () => {
    const err = () => createElement(1, null);
    assert.throws(err, 'The element type must be a string');
  });

  check('it throws errors when the options argument is not an object', () => {
    const err = () => createElement('h1', null);
    assert.throws(err, 'The options argument must be an object');
  });

  xcheck('it creates DOM elements', () => {
    // do some testing
  });

  xcheck('it mounts DOM elements', () => {
    // do some testing
  });
});
```

When the tests run, we will see this:

![test output](https://thepracticaldev.s3.amazonaws.com/i/ec7qvth3ovdxlrw65e88.png)

The work in the tests is being done by `group`, `check`, `xcheck` and `assert`. We will begin by examining `group` and `check`. *Note: some code has been omitted for brevity*

```js
function group (title, fn) {
  console.log(title);
  fn();
}

function check(title, fn) {
  console.log(title);
  try {
    fn();
  } catch (e) {
    console.log(title);
    console.log((e.message);
    console.log(e.stack);
  }
};
```

The behaviour of both methods is simple but they introduce two new things we have not covered before: **`try/catch`** and **`arrow functions`**.

In the last post we had our first foray into error handling via the `throw` keyword. `Try/catch` is another error handling feature. It is used to test a block of code for errors and then handle any exceptions. In the `check` function, the `try` block will execute the function that has been passed in as the second argument. Any execptions are sent to the catch block, which is passed an [error object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error). This object contains a message describing the error and a stack trace indicating which line of code caused it. Note: the variable `e` passed to the `catch` block is block scoped, so this code would result in an error:

```js
function errorWaitingToHappen() {
  try {
    throw Error('I am an error');
  } catch (errorWithLongVariableName) {
    console.log(errorWithLongVariableName)
  }

  console.log(errorWithLongVariableName) // this is undefined
}

errorWaitingToHappen(); // Uncaught ReferenceError: errorWithLongVariableName is not defined
```

Arrow functions were introduced in ES6 and generally speaking, they behave like normal functions except in [these](https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20&%20beyond/ch2.md#not-just-shorter-syntax-but-this) [cases](https://stackoverflow.com/questions/34361379/are-arrow-functions-and-functions-equivalent-exchangeable). We are using them because none of the issues outlined in the links are applicable and they make our code terser.

Our next lesson comes not from the testing library we are building but from the code being tested. `createElement` should throw exceptions when called with certain arguments. Testing this behaviour requires us to pass it to `assert.throws` with those arguments specified. We could do it like this:

```js
assert.throws(createElement, ['h1', null], 'The options argument must be an object');
```

And then implement `assert.throws`:

```js
throws(fn, args, errMsg = '') {
  try {
    fn(...args);
  } catch (e) {
    // do something
  }
}
```

By passing in the error inducing arguments in an array, we can [spread](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) them into `createElement` to successfully trigger the exception. However, this is not an optimal solution because it pollutes our `assert.throws` implementation. That function should not care about the arguments needed to throw an exception. Its sole purpose is to execute the function and check for errors. We can achieve that goal by introducing another new concept: **lazy evaluation**:

```js
const err = () => createElement('h1', null);
assert.throws(err, 'The options argument must be an object');
```

Even though we are calling `createElement` in the function assigned to `err`, we need not worry because the actual execution happens within the context of `assert.throws`. It is only evaluated when needed, hence the term. Had we written this:

```js
assert.throws(createElement('h1', null), 'The options argument must be an object');
```

`createElement` will be called in the context of `check`. And because `check` has its own `try/catch` block, it would handle the exception, meaning that `assert.throws` would never run and our test would fail when it should have passed. With that sorted, we can fill out the rest of `assert.throws`.


```js
throws(fn, errMsg = '') {
  const didNotThrowErr = new Error('The supplied function didn\'t throw an error');

  try {
    fn();
    throw didNotThrowErr;
  } catch (e) {
    if (e === didNotThrowErr) throw didNotThrowErr;

    if (!errMsg || e.message === errMsg) return true;

    throw new Error(`\n\nFound: ${e.message}\nWanted: ${errMsg}\n\n`); 
  }
}
```
___

# Checking for object equality

The implementation of `assert.isDeeplyEqual` raises some interesting questions. As we can see below, its purpose is to check if any two given objects are equal.

```js
check('it creates a virtual dom object', () => {
  const target = createElement('div', {});
  const copy = { type: 'div', attrs: {}, children: [] };
  assert.isDeeplyEqual(target, copy);
}); 
```

What kind of equality are we checking for here? Compound values (objects, arrays etc) in JavaScript are [assigned by reference](https://medium.com/@naveenkarippai/learning-how-references-work-in-javascript-a066a4e15600), so using the in-built equality operators will not help us. We want to ensure both objects contain the same properties and that those properties also contain the same values. Here are some questions our solution needs to answer:
- Are both values objects?
- Do they contain the same number of properties? 
- Do all those properties hold the same value? If they do:
  - Are they primitive values we can easily check using equality operators?
  - If they are compound values, what do we do?
  - Are we going to handle every type of compound value?

We will call the function which does the checks `deepEqual` and the first thing we need to do is check if we are dealing with objects. This check will be repeated again later so we can abstract it away with the following utility function:

```js
function getLengthAndType(obj) {
  if (Object.prototype.toString.call(obj) === '[object Array]') {
    return { type: "array", length: obj.length }
  }

  if (Object.prototype.toString.call(obj) === '[object Object]') {
    return { type: "object", length: Object.keys(obj).length }
  }

  return null;
}
```

We return an object with useful information we will use later and it also helps us avoid repeating the `Object.prototype.toString.call` call. Here is the utility in action in the first part. 

```js
function deepEqual(obj, comparisonObj) {
  const objInfo = getLengthAndType(obj);
  const comparisonObjInfo = getLengthAndType(comparisonObj); 

  // only go forward with arrays or objects
  if ( !objInfo || !comparisonObjInfo) {
    return false
  }

  if (objInfo.length !== comparisonObjInfo.length || objInfo.type !== comparisonObjInfo.type) {
    return false
  }
```

`getLengthAndType` returns `null` for non-arrays and non-objects, so we can quickly establish the type of values we are comparing since `null` is a falsy value. After that, we check the length and ensure both objects are the same type. The next thing to consider is how we are going to iterate over our array or object, and check each value.

```js
if (objInfo.type === 'array') {
  for (var i = 0; i < objInfo.length; i++) {
    if (compare(obj[i], comparisonObj[i]) === false) return false;
  }    
} else {
  for (let [key] of Object.entries(obj)) {
    if (compare(obj[key], comparisonObj[key]) === false) return false;
  } 
}
```

Using a `for` loop allow us to iterate over the array and check the values easily. However, this solution does have one drawback in that it assumes the values we want to check share same index position in both arrays. This is fine for our use case because we are checking that the objects defined in the `children` array of our virtual dom object are in the same position. `Object.entries` returns an array of an object's `[key, value]` pairs. Coupled with `for..of`, which creates a loop over [iterable objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterable_protocol), we can also iterate over an object. An alternative approach would have been to use `for..in` but this would require an additional `hasOwnProperty` check because `for..in` also iterates over [inherited](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) properties, something `Object.entries` does not do. This alternative approach would look like this:

```js
if (objInfo.type === 'array') {
  for (var i = 0; i < objInfo.length; i++) {
    if (compare(obj[i], comparisonObj[i]) === false) return false;
  }    
} else {
  for (var prop in obj ) {
    if (obj.hasOwnProperty(prop)) {
      if (compare(obj[prop], comparisonObj[prop]) === false) return false;
    }
  }  
}
```

The most interesting thing about `deepEqual` is the concept it introduces in its internal `compare` function. As you can see below, we easily compare functions and primitive values with the `toString()` method and equality operators, respectively. However, comparing arrays or objects is more complicated. 

```js
const compare = (val, comparisonVal) => {
  const isArrayOrObject = getLengthAndType(val);
  const isFunction = Object.prototype.toString.call(val) === '[object Function]';
  
  if (isArrayOrObject) {
    if (!deepEqual(val, comparisonVal)) return false;
  } 
  
  else {
    if (isFunction) {        
      if (val.toString() !== comparisonVal.toString()) return false;
    } else {
      if (val !== comparisonVal) return false;
    }
  }
};
```

Up until now, we have been declaring our functions in one place and then calling them in another. With `deepEqual`, we are calling it within itself. This process is called [**recursion**](https://github.com/getify/Functional-Light-JS/blob/master/manuscript/ch8.md/#chapter-8-recursion) and makes `deepEqual` a *recursive function*. Given this call `isDeeplyEqual( {arr: [1, 2, 3]}, {arr: [1, 2, 3]} )`, when the program reaches the `compare` function, this happens:

```js
// The initial recursive call
const compare = (val, comparisonVal) => {
  // val is {arr: [1, 2, 3]}
  // comparisonVal is {arr: [1, 2, 3]}
  const isArrayOrObject = getLengthAndType(val); // { type: "object", length: 2 }
  
  if (isArrayOrObject) { // true
    if (!deepEqual(val, comparisonVal)) return false; // recursion! 
  }
  //...
}

// We reach compare again and make another recursive call
const compare = (val, comparisonVal) => {
  // val is [1, 2, 3]
  // comparisonVal is [1, 2, 3]
  const isArrayOrObject = getLengthAndType(val); // { type: "array", length: 3 }
  
  if (isArrayOrObject) { // true
    if (!deepEqual(val, comparisonVal)) return false; // more recursion! 
  }
  //...
}

// No more recursive calls 
// We are now comparing every element in the array [1, 2, 3]
const compare = (val, comparisonVal) => {
  // val is 1
  // comparisonVal is 1
  const isArrayOrObject = getLengthAndType(val); // false
  
  if (isArrayOrObject) { // false
    if (!deepEqual(val, comparisonVal)) return false; // no recursion :-(
  }
  //...
}
```

Recursion is a good way to navigate and perform operations on nested data structures. In our scenario it abstracts away the complexity of writing conditional checks to handle what will be an unknown number of nested objects and arrays. Each time we call a function, a new [execution context](http://davidshariff.com/blog/what-is-the-execution-context-in-javascript/) is created and added to the [call stack](http://davidshariff.com/blog/what-is-the-execution-context-in-javascript/), so we can offload that work to the JavaScript engine and wait for whatever is returned. Put together, `deepEqual` looks like this:

```js
function getLengthAndType(obj) {
  if (Object.prototype.toString.call(obj) === '[object Array]') {
    return { type: "array", length: obj.length }
  }

  if (Object.prototype.toString.call(obj) === '[object Object]') {
    return { type: "object", length: Object.keys(obj).length }
  }

  return null;
}

function deepEqual(obj, comparisonObj) {
  const objInfo = getLengthAndType(obj);
  const comparisonObjInfo = getLengthAndType(comparisonObj); 

  // only go forward with arrays or objects
  if ( !objInfo || !comparisonObjInfo) {
    return false
  }

  if (objInfo.length !== comparisonObjInfo.length || objInfo.type !== comparisonObjInfo.type) {
    return false
  }
  
  const compare = (val, comparisonVal) => {
    const isArrayOrObject = getLengthAndType(val);
    const isFunction = Object.prototype.toString.call(val) === '[object Function]';
    
    if (isArrayOrObject) {
      if (!deepEqual(val, comparisonVal)) return false;
    } 
    
    else {
      if (isFunction) {        
        if (val.toString() !== comparisonVal.toString()) return false;
      } else {
        if (val !== comparisonVal) return false; // we are comparing primitive values
      }
    }
  };

  if (objInfo.type === 'array') {
    for (var i = 0; i < objInfo.length; i++) {
      if (compare(obj[i], comparisonObj[i]) === false) return false;
    }    
  } else {
    for (let [key] of Object.entries(obj)) {
      if (compare(obj[key], comparisonObj[key]) === false) return false;
    }
  }

  return true; // nothing failed
}
```

So far we have only looked at the code responsible for the actual testing but how do we show the test results and other information to the user? Unlike our framework which will be used to create user interfaces in the browser, our testing framework only works on the command line. Let us begin with the final implementations of `check` and `group`. `xcheck` is also included but it is not doing much.

```js
const colors = require('colors');
const assert = require('./assertions');

const repeat = (str, n) => Array(n).join(str);
const indent = n => repeat('    ', n);
const indentLines = (str, n) => indent(n) + str.replace(/\n/g, `\n${indent(n)}`);
const log = str => console.log(str);
const summary = { success: 0, fail: 0, disabled: 0 };

let indentLevel = 0;
let examinar;

function group(title, fn) {
  indentLevel++;
  log(`\n${indent(indentLevel)}⇨ ${title}`.yellow);
  fn();
  indentLevel--;
}

function check(title, fn) {
  try {
    fn();
    log(`${indent(indentLevel + 1)}${' OK '.bgGreen.black} ${title.green}`);
    summary.success++;
  } catch (e) {
    log(`${indent(indentLevel + 1)}${' FAIL '.bgRed.black} ${title.red}`);
    log(indentLines(e.message.red, indentLevel + 1));
    log(indentLines(e.stack.red, indentLevel + 1));
    summary.fail++;
  }
}

function xcheck(title) {
  log(`${indent(indentLevel + 1)}${' DISABLED '.bgWhite.black} ${title.gray}`);
  summary.disabled++;
}

function end() {
  log(`\n${repeat('.', 60)}\n`);
  log('Test summary:\n');
  log(`    Success: ${summary.success}`.green);
  log(`    Fail: ${summary.fail}`.red);
  log(`    Disabled: ${summary.disabled}\n\n`.gray);

  if (summary.fail > 0 ) process.exit(1);
  process.exit(0);
}

module.exports = { assert, check, end, group, xcheck };
```

Displaying the results and relevant information is achieved through a combination of indentation, adding colour and summarisation. Each time we group some tests, we log the title using [**template literals**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) and the [color](https://github.com/Marak/colors.js) module. You will notice that this is the first time in our project we are using a third party library. This is because as much as we are trying to write our own code, that is not always possible and there are instances where, because of time or complexity, it makes more sense to leverage existing solutions. However, when using third-party code it does help to understand a high level what exactly is being abstracted away. The color module works by extending `String.prototype` and defining getters which return the specified colour. You can test this yourself by importing the module and writing `console.log(''.__proto__)`. Template literals can evaluate expressions, so combined with the colour module, they create nice visuals for our tests.

The `end` function terminates our suite of tests after logging a summary. This is achieved by using the globally available `process.exit()` method which instructs Node.js to terminate the current process synchronously with an exit code: `1` for failure and `0` for success.

There is one more thing we can learn from the code at end of our function: **exporting modules**. We will visit this topic in greater depth in our next post when set up our development environment but for now, we can briefly cover it. Modules are units of code, much like lego pieces, which can be put together to create various things. Node treats each file as a separate module. You import files using the `require` keyword and export with `module.exports` or `exports`. Both `module.exports` or `exports` reference the same object so:

```js
module.exports = { assert, check, end, group, xcheck };
```

is equivalent to:

```js
exports.check = check();
exports.end = end();
exports.assert = assert;
exports.group = group();
exports.xcheck = xcheck()
```
___ 

## Summary

In this post we made no progress with the actual framework code but we began laying a foundation for future development. In creating a testing framework we learnt about **arrow functions**, **try/catch**, **lazy evaluation**, **recursion**, **template strings** and **exporting modules**. All these lessons were brief but powerful nonetheless because we did them in the context of a very hands on project. This approach makes the concepts more concrete. 

___ 

### Resources
#### Testing Tutorial: https://medium.freecodecamp.org/learnbydiy-how-to-create-a-javascript-unit-testing-framework-from-scratch-c94e0ba1c57a
#### Object Equality Tutorial: https://gomakethings.com/check-if-two-arrays-or-objects-are-equal-with-javascript