# Unit Testing using Mocha, Sinon, and Chai in NodeJS

We use Mocha as the test framework, Chai for assertions, and Sinon.js for creating Doubles. Here's the code for installation:

```bash
$ npm install sinon mocha chai --save-dev
```

`--save-dev` is used because these modules are only needed during development. We don't need these modules during production.

# The Script

The main thing to do when creating a script is to determine which function to test, along with what will be tested in that function.

For example, if we want to do unit testing on the following function:

```javascript
/*number-lib.js*/

function addInteger(x, y) {
    if (Number.isInteger(x) && Number.isInteger(y)) {
        return x + y   
    }
    else {
        return 'Number is not integer'
    }
    return 
}
module.exports = {
    addInteger
}
```

The function above is a function to add integers x and y. If they are not integers, it will return a string saying that the input is not an integer.

First, we will determine our test scope as follows:

```javascript
// number-list-test.spec.js
const numberLib = require('./number-lib.js')

describe('Add integer', () => {
    it('should return 23', () => {
       // Arrange
       ...
       // Act
       ...
       // Assert
       ...
    })
    it('should return "Number is not integer", because input is not integer', () => {
       // Arrange
       ...
       // Act
       ...
       // Assert
    })
})
```

`describe()` and `it()` are functions of Mocha, where we will write our test script. In the `describe()` function, we determine our test scope. Inside each `it()` function, we determine the cases that we will perform.

In the case above, our scope is to test the `addInteger()` function with 2 agreed-upon cases, namely the function must return 23, and the function must return `"Number is not integer"` because the input we will give is not an integer.

In the `it()` function, there is an Arrange, Act, and Assert section. Here is an explanation:

1. **Arrange**
    

In the Arrange section, it usually contains input initialization and doubles initialization which aims to produce the desired output.

1. **Act**
    

In the Act section, we execute the function that we want to test using the input that we have initialized in the Arrange section.

1. **Assert**
    

In the Assert section, we check whether the result produced by the function in the Act section matches the expected output that we have initialized in the Arrange section.

Now we will add the code snippet above to make it complete.

```javascript
// number-list-test.spec.js
const numberLib = require('./number-lib.js')
const expect = require('chai').expect

describe('Add integer', () => {
    it('should return 23', () => {
       // Arrange
       let x = 11
       let y = 12
       // Act
       let result = numberLib.addInteger(x, y)
       // Assert
       expect(result).to.equal(23)
    })
    it('should return "Number is not integer", because input is not integer', () => {
       // Arrange
       let x = 1.1
       let y = 1.2
       // Act
       let result = numberLib.addInteger(x, y)
       // Assert
       expect(result).to.equal('Number is not integer')
    })
})
```

## Doubles

For example, let's update the function we want to test like this:

```javascript
function addNumber(x, y) {
    if(isInteger(x) && isInteger(y))) {
        return x + y
    }
    else if(isDecimal(x) && isDecimal(y))) {
        return Math.round(x) + Math.round(y)
    }
    else {
        return 'The input is not integer nor decimal value'
    }
}

function isInteger(x) {
    ...
}

function isDecimal(x) {
    ...
}

modules.export = {
    addInteger
}
```

Correct. There is a function inside a function. The concept of Unit Testing is that we only test the function that we want to test. If the function calls another function, then we ignore the other function by making it a Double.

In our example, we will not care about the `isInteger()` and `isDecimal()` functions. Both of these functions will be made into doubles with the help of sinon.

In that case, it was agreed that there are 3 cases to be tested. Here are the tests:

```javascript
const sinon = require('sinon')
const expect = require('chai').expect
const numberLib = require('./number-lib')

describe('Add Number', () => {
    it('should return 40', () => {
    })
    it('the input is decimal, should return 15', () => {
    })
    it('should return "The input is not integer nor decimal value"', () => {
    })
})
```

We have determined the cases, now let's add the test script:

```javascript
const sinon = require('sinon')
const expect = require('chai').expect
const numberLib = require('./number-lib')

describe('Add Number', () => {
    it('should return 40', () => {
        //Create new stub for isInteger that always return true
        let isInteger = sinon.stub(numberLib, 'isInteger').returns(true)
        let x = 20
        let y = 20
        let result = numberLib(x, y)
        expect(result).to.equal(40)
        isInteger.restore()
    })
    it('the input is decimal, should return 15', () => {
        //Create new stub for isInteger that always return false
        //stub for isDecimal always return true
        let isInteger = sinon.stub(numberLib, 'isInteger').return(false)
        let isDecimal = sinon.stub(numberLib, 'isDecimal').returns(true)
        let x = 7.4
        let y = 7.6
        let result = numberLib(x, y)
        expect(result).to.equal(15)
        isInteger.restore()
        isDecimal.restore()
    })
    it('should return "The input is not integer nor decimal value"', () => {
        //Create new stub for isInteger that always return false
        //stub for isDecimal always return false
        let isInteger = sinon.stub(numberLib, 'isInteger').return(false)
        let isDecimal = sinon.stub(numberLib, 'isDecimal').returns(false)
        let x = 'anything'
        let y = 'anything'
        let result = numberLib(x, y)
        expect(result).to.equal('The input is not integer nor decimal value')
        isInteger.restore()
        isDecimal.restore()
    })
})
```

At the beginning of each case, we always initialize the stub for the `isInteger()` and `isDecimal()` functions, by changing its return value according to the needs of the test case. When `numberLib()` is called, if a stub function we made before is encountered during execution, that function will be changed to a stub.

At the end of each case, we must restore() the stubs we made so that the functions that became stubs can run their own content again. If we don't do a `restore()`, there is a possibility that the next test will fail because these functions are still stubs.

The above test script can still be improved with the following Mocha functions:

```javascript
...
const numberLib = require('./number-lib')
describe('Add number', () => {
    let isInteger
    let isDecimal
    beforeEach(() => {
        isInteger = sinon.stub(numberLib, 'isInteger')
        isDecimal = sinon.stub(numberLib, 'isDecimal')
    })
    afterEach(() => {
        isInteger.restore()
        isDecimal.restore()
    })
    it('...', () => {
        isInteger.returns(true)
        ...
    })
    it('...', () => {
        ...
    })
    it('...', () => {
        ...
    })
})
```

`beforeEach()` is always called before each test case is executed, while `afterEach()` is always called after each test case is executed. With the help of `beforeEach()` and `afterEach()` we can avoid repetitive initialization code and make our test script more readable.

## Anonymous function cases

Sometimes we encounter something like this:

```javascript
//my-route.js
router.get('/', (req, res) => {
    //function that we want to test
    ...
    ...
})
router.get('/user', (req, res) => {
    //other function
    ...
    ...
})
module.exports = router
```

The code above is one of the examples to create a router on `express.js`.

How do we test that function, when that function can't be called from another function or so it is called Anonymous Function? We can test it with Sinon's help.

First, create the test script:

```javascript
const route = ('./my-route')

describe('Route test', () => {
    it('should ...', () =>{
        
    })
})
```

We can see from the function, `router` call `get` function that has 2 parameters. First parameter is the endpoint of the route, and the second is the function that we want to test. So we need to create spy on `get` function.

```javascript
const route = require(./my-route)
const sinon = require('sinon')

describe('Route test', () => {
    it('should ...', () => {
      //create spy on get
      let get = sinon.spy(route, 'get')
      // Arrange input 
      let req = {}
      let res = {}
      //call the function
      route.router()
      ..
    })
}
```

When `route.router()` called, all function (`router.get('/')` dan `router.get('/user')`) will be called. Because we only want to test `get('/')`, the test code become like this:

```javascript
const route = require(./my-route)
const sinon = require('sinon')

describe('Route test', () => {
    it('should ...', () => {
      let get = sinon.spy(route, 'get')
     
      let req = {}
      let res = {}
      
      route.router()
      //Calling the `first` get and then trigger anonymous function on the parameter no 1
      get.firstCall.callArgWith(1, req, res)
      
      expect(...)
      get.restore()
    })
}
```

`get.firstCall` will get only the function that is called first.

`get.firstCall.callArgwith(1, req, res)` will be triggering the function on second parameter (Zero based). And then, we succesfully call the anonymous function, asserting could be done.

# Running

To run the test script, we need some modifications on `package.json`

```javascript
// package.json
"scripts": {  
    "test": "mocha test/**/*.spec.js"
}
```

The goal is to running `mocha` command on all files in all folders that having `.spec.js` extension. So we need to name our test function with `.spec.js`

Run the script:

```bash
npm test
```

We can see the results in terminal:

```javascript
> mocha test/**/*.spec.js

  Add Integer
    ✓ should return 23
    ✓ should return "Number is not integer", because input is not integer
    
 2 passing (1s)
```

# Reporting

We need to module for this. Mocha-junit-reporter for report unit-test, and nyc for code coverage

```bash
$ npm install mocha-junit-reporter nyc --save-dev
```

Modify `package.json` on `scripts`:

```javascript
"scripts": {
    "report": "mocha test/**/*.spec.js --reporter mocha-junit-reporter",
    "coverage": "nyc --reporter=lcov --reporter=text-lcov npm test"
},
```

Run it:

```bash
$ npm run report
$ npm run coverage
```

We will get `.xml` that we can show on any other application or tool like Jenkins

# References

[https://martinfowler.com/articles/mocksArentStubs.html](https://martinfowler.com/articles/mocksArentStubs.html)

[https://www.sitepoint.com/sinon-tutorial-javascript-testing-mocks-spies-stubs/](https://www.sitepoint.com/sinon-tutorial-javascript-testing-mocks-spies-stubs/)
