# Testing and dependency injection

## Outline

### Objective

* Understand functions
* Learn what it means to test
* Create a dependency injection library for modularity
* Write a simple testing framework
* Test our render function
* Test our action creator function

### Technologies used

* Javascript

### Design Patterns

* Dependency Injection
* Inversion of control

### What we will do

* Discuss Functions in Javascript and the various ways we can create and run them
* Create our own module system using the dependency injection (DI) and inversion of control (IOC) patterns
* Write a test for a render function
* Use IOC to mock and stub

## Pre Session

Can Watch
* [Dependency Injection basics- Fun Fun Function - YouTube](https://www.youtube.com/watch?v=0X1Ns2NRfks)
* [Inversion of Control - Fun Fun Function - YouTube](https://www.youtube.com/watch?v=-kpEP4JeEdc)
Should Watch
* [Unit tests vs. Integration tests - FunFunFunction  - YouTube](https://www.youtube.com/watch?v=vqAaMVoKz1c)
Summary Unit test for one thing Integration test for more than one thing

## Session

By the end of the last session we have completed our application cycle

Data - renders -> View - seen -> User - triggers -> Action - updates -> Data

Our application still has some optimization problems but before we go one we need to take a moment to talk about technical debt.

__Technical debt__ is a concept in programming that reflects the extra development work that arises when code that is easy to implement in the short run is used instead of applying the best overall solution.

In your introduction to programing course you may have come across the concept of O(n) for time and space complexity as a measure of efficiency.

While runtime and space efficient are essential, when it comes to developing applications (especially in the beginning), a third metric which we will refer to as **maintainability** is worth considering.

Maintainability affects development efficiency by impacting how fast a team or single programer can read, write, refactor and remove features in an application.

> For most programers writing code is easier then reading it.

Any team who had ever worked on a mid to large project or inherited the work of another team, have at least once suggested to re-write the entire code base from scratch.

Something about a fresh clean canvas creates a very tempting feeling of freedom. This sense of freedom stems from the liberation from the constraint placed by the previous code.

This is because every feature that is written, in an application, has a hidden cost.

- a feature that is poorly untested can conflict with future features.
- a feature that is unreadable is indecipherable to new programer, increasing the development time.

In this session we will talk about testing as well as some concepts that make our code more easy to reason about and therefor more readable.

### Testing

When we refer to testing, we typically mean automated testing. While it is possible to rely on human testers, and in fact some feature tests, can only be carried out by a human tester. However, automated tests are faster for every other case.

Tests are important for the following scopes

- Long term; as our project grows in size the more likely we are to break old code, automated tests hep us check that code we wrote did not affect other parts of the project.
- Short term; writing tests before, while or after developing a feature forces us to think about how our code will be consumed, adding a degree of confidence, when delivering our code, that it works.

When developing with tests, it helps to be very skeptic of our own code. Any experienced programer knows that most bugs originate from human error (who else wrote that buggy code), and often that human is us.

Humans err. We can not see the full picture all the time. We have biases and gaps in our logic. We make  mistakes. Writing tests allows us to present definitive proof not just to others but to our self that our code works (at least as much as we tested it).

> We write tests because we do not trust ourselves to not make mistakes.


### Testable code

We will be building a shopping cart service.
This is the API our Cart service provides externally.

```js
const Cart = {
	add(product) { ... }, // add a product to the cart or if it is already there add to its count
	remove(product) { ... }, // remove one count of a product or remove it completely from the cart
	removeAll(product) { ... }, // remove all counts of product
	get(product) { ... }, // gets an item in the cart by product
	getItems() { ... }, // returns all cart items
}
```

#### Side Effects

> noun `:` a secondary, typically undesirable effect of a drug or medical treatment.

```js
var items = []
function addItem(product) {
	items.push({
		product,
		count: 1,
		price: product.price
	})
}
```

We say that a function has side effects when it does anything other than just returning an output.

Side effects are not bad, but we try to minimize them in our system because.

1. functions with side-effect adds some complexity to code when programers reason about them.
2. functions with side effects are harder to test.

```js
function addItem(items, product) {
	let items = items.slice();
	items.push({
		product,
		count: 1,
		price: product.price
	})
	return items
}
```

#### State and Side Effects

Side effects can not be avoided when it comes to acquisition, storage, or retrial of data (state).
No meaningful code outside of utility functions will not be manipulating some kind of state.

```js
let Cart = {
	items: [],
	add(product) {
		this.items.push({
			product,
			count: 1,
			price: product.price
		})
	}
}
```

__I/O__
- Waiting for user input `prompt("Say Hi")` or output `console.log("Hello")`
- rendering to some interface `document.write("Hello")` `document.body.innerHTML = "<h1>Hello</h1>"`
Our cart service preserves a state.

__Time__

- Date.now()

__getting a random value__

- Math.random()





There are fundamentally 2 ways of testing code; unit tests and integration tests.

Unit tests are about testing a piece of code in isolation

```js
// Code for incrementing the count of a cart item in an e-commerce site

function incrementItem(item) {
	let count = item.count + 1
	let price = item.product.price * count
	return Object.assign({}, item, { count, price })
}

// Increment Item Test

actual = incrementItem(item1).count
expected = 2

var incrementItemTest = actual === expected

console.log(`Increment Item ${incrementItemTest?`Works`:`Failed`}`);
```

```js
// Code increasing

function getItem(cart, product) {
	return Object.assign({}, cart, {
		items: addItem(cart.items, product)
	})
}

// Increment Item Test

actual = incrementItem(item1).count
expected = 2

var incrementItemTest = actual === expected

console.log(`Increment Item ${incrementItemTest?`Works`:`Failed`}`);
```

```js

// code

function addItem(items, item) {
	return items.slice().push(item)
}

// Test Code Setup

let actual, expected;
let item1 = { product: { name: 'eggs' ,price: 20 }, count: 1}
let item2 = { product: { name: 'milk' ,price: 13 }, count: 1}
let items = [ item1 ]

// Add item Test

actual = addItem(items, item2)
expected = [ item1, item2 ]

var addItemTest =  JSON.stringify(actual) === JSON.stringify(expected)

console.log(`Add item ${addItemTest?`Works`:`Failed`}`);
```

Integration tests are about checking that our code works with other parts of our code or external library.

```js

// code

function addProducts(cart, product) {
	return items.slice().push(item)
}

// Test Code Setup

let actual, expected;
let item1 = { product: { name: 'eggs' ,price: 20 }, count: 1}
let item2 = { product: { name: 'milk' ,price: 13 }, count: 1}
let items = [ item1 ]

// Add item Test

actual = addItem(items, item2)
expected = [ item1, item2 ]

var addItemTest =  JSON.stringify(actual) === JSON.stringify(expected)

console.log(`Add item ${addItemTest?`Works`:`Failed`}`);
```

### Pure functions

A pure function is a function that has no side-effects it follows the mantra of `in => out`.
A pure function has some guarantees.

- A pure function will always give the same output for a given input.
	- Pure function fn(x) will always output the same y given the same value of x.
	- Pure function fn(x) will not include code that relies on external state eg. global variables, IO, Time, or random number generation.
- A pure function has no side-effects
	- The function does not modify the value of its input arguments.
	- The function does not change a global state value, or trigger IO

These 2 rules create very nice properties in any system that uses them including.

- The ability to compose our code with ease.
- The ability to optimize through memoizing.
- The ability to run parallel code.
- produces easy to reason about code.
- produces easily testable code.

This does not mean all our code should be pure
