Skip to content
Permalink
Browse files
sample solutions and error fixes for 07_unit_testing
  • Loading branch information
bordasb committed Oct 25, 2020
1 parent 059e611 commit 276525ad90f0824b1384fb4ee23ec7cd9e544ff4
Show file tree
Hide file tree
Showing 6 changed files with 521 additions and 13 deletions.
@@ -17,7 +17,7 @@ We will be using a tool called [Jest](https://jestjs.io) which was originally de

READ THE FOLLOWING CAREFULLY

**In the previous labs you have opened the `foundation` directory in VS Code and hd access to all the files and subfolders. The testing tools require you to open the folder containing the project we want to test directly so you will need to use the file menu and open the `foundation/exercises/07_unit_testing/todo/` directory.**
**In the previous labs you have opened the `foundation` directory in VS Code and had access to all the files and subfolders. The testing tools require you to open the folder containing the project we want to test directly so you will need to use the file menu and open the `foundation/exercises/07_unit_testing/todo/` directory.**

The project has a number of node package dependencies which are listed in the `package.json` file. Start by installing all of these and then you should start the server and have a look at the website. As you can see it is a simple todo list, try adding a few items and deleting them, you will see that only some of the functionality has been implemented!

@@ -110,7 +110,7 @@ Any code highlighted in red is not covered by your test suite.

### 1.4 Running the Tests Using Visual Studio Code

In the previous section you learned how to run a test suite and check code coverage just using the CLI (terminal) and this will work regardless of the environment you are using. In this section you will learn how to run your test suite using VS Code together with a feww useful extensions.
In the previous section you learned how to run a test suite and check code coverage just using the CLI (terminal) and this will work regardless of the environment you are using. In this section you will learn how to run your test suite using VS Code together with a few useful extensions.

### 1.5 Visual Studio Code Extensions

@@ -268,11 +268,11 @@ Here is a possible solution:
```javascript
module.exports.add = (item, qty) => {
qty = Number(qty)
if(isNaN(qty)) throw new Error('the quantity must be a number')
if(isNaN(qty)) throw new Error('qty must be a number')
let flag = false
for(let index in data) {
if (data[index].item === item) {
data[index].qty+= qty
data[index].qty += qty
flag = true
}
}
@@ -295,11 +295,11 @@ There is not a lot we can do to the program code:
```javascript
module.exports.add = (item, qty) => {
qty = Number(qty)
if(isNaN(qty)) throw new Error('the quantity must be a number')
if(isNaN(qty)) throw new Error('qty must be a number')
let flag = false
for(const index in data) {
if (data[index].item === item) {
data[index].qty+= qty
data[index].qty += qty
flag = true
}
}
@@ -327,7 +327,7 @@ Run the test suite to check there are no errors.
})
```

Again, check that all the test still pass. As a final check start the web server and see if it works in the browser. Congratulations, you have now completed your first TDD iteration.
Again, check that all the tests still pass. As a final check start the web server and see if it works in the browser. Congratulations, you have now completed your first TDD iteration.

### 1.8 Test Your Understanding

@@ -378,7 +378,7 @@ In this version we export a NodeJS [Class](https://developer.mozilla.org/en-US/d

The `unit tests/todo.js` script contains the same tests as the array-based version but each test now:

1. Uses an object constructor to get a `ToDo` object asynchronouly.
1. Uses an object constructor to get a `ToDo` object asynchronously.
2. Calls the appropriate async method(s).

### 2.2 In-Memory Databases
@@ -419,10 +419,10 @@ You will now complete a few more TDD iterations:

1. What happens if you leave the item box empty? This should throw an error, not add a blank item.
2. What happens if you leave the qty box empty? Solve this in a similar way.
3. What happens if you click on one of the **Delete** links? Implement this feature. Remember that since this is testing the `delete()` function you need to create a new _test suite_ called `delete()` in the same test suite.
3. What happens if you click on one of the **Delete** links? Implement this feature. Remember that since this is testing the `delete()` function you need to create a new _test suite_ called `delete()` in the same unit test script.
4. Can you write one or more tests for the `getAll()` function?
5. And for the `clear()` function as well.

As before, are you gettting 100% code coverage? If not, write more tests. Are you covering the edge cases and checking for correct handling of bad data?

Note that there appears to be a bug in the VS Code debugger when stepping through a function that returns a promise. If the debugger sends you to a script called `async_hooks.js` you can get stuck in an loop. When the debugger is highlighting the closing brace of an async function press the **Step Out** button a few times (typically) to return to the parent function. Remember you can add breakpoints to the test as well as the module code.
Note that there appears to be a bug in the VS Code debugger when stepping through a function that returns a promise. If the debugger sends you to a script called `async_hooks.js` you can get stuck in a loop. When the debugger is highlighting the closing brace of an async function press the **Step Out** button a few times (typically) to return to the parent function. Remember you can add breakpoints to the test as well as the module code.
@@ -17,9 +17,7 @@ module.exports = class ToDo {
async add(item, qty) {
qty = Number(qty)
if(isNaN(qty)) throw new Error('the quantity must be a number')
let sql = 'SELECT * FROM items;'
// const dataAll = await this.db.all(sql)
sql = `SELECT * FROM items WHERE ITEM = "${item}"`
let sql = `SELECT * FROM items WHERE ITEM = "${item}"`
const data = await this.db.all(sql)
if(data.length === 0) {
sql = `INSERT INTO items(item, qty) VALUES("${item}", ${qty})`
@@ -49,4 +47,7 @@ module.exports = class ToDo {
return data.items
}

async clear() {
await this.db.run('DELETE FROM items')
}
}
@@ -0,0 +1,59 @@

'use strict'

const sqlite = require('sqlite-async')

module.exports = class ToDo {

constructor(dbName = ':memory:') {
return (async() => {
this.db = await sqlite.open(dbName)
const sql = 'CREATE TABLE IF NOT EXISTS items(id INTEGER PRIMARY KEY AUTOINCREMENT, item TEXT, qty NUMERIC)'
await this.db.run(sql)
return this
})()
}

async add(item, qty) {
if (item === '') throw new Error('item cannot be empty')
if (qty === '') throw new Error('qty cannot be empty')
qty = Number(qty)
if(isNaN(qty)) throw new Error('the quantity must be a number')
let sql = `SELECT * FROM items WHERE ITEM = "${item}"`
const data = await this.db.all(sql)
if(data.length === 0) {
sql = `INSERT INTO items(item, qty) VALUES("${item}", ${qty})`
await this.db.run(sql)
} else {
const newQty = data[0].qty + qty
sql = `UPDATE items SET qty=${newQty} WHERE ITEM = "${item}"`
await this.db.run(sql)
}
}

async getAll() {
const sql = 'SELECT * FROM items'
const data = await this.db.all(sql)
console.log(data)
return data
}

async delete(id) {
if (id === undefined) throw new Error('id cannot be undefined')
id = Number(id)
if (isNaN(id)) throw new Error('id must be a number')
const sql = `DELETE FROM items WHERE id=${id}`
if ((await this.db.run(sql)).changes === 0) throw new Error('id has to exist')
}

async countItems() {
const sql = 'SELECT COUNT(*) as items FROM items'
const data = await this.db.get(sql)
return data.items
}

async clear() {
await this.db.run('DELETE FROM items')
}

}
@@ -0,0 +1,163 @@

'use strict'

const ToDo = require('../modules/todo.js')

beforeAll( async() => {
// stuff to do before any of the tests run
})

afterAll( async() => {
// runs after all the tests have completed
})

describe('add()', () => {
// block of tests
// beforeEach( async() => {
// todo.clear()
// })
afterEach( async() => {
// runs after each test completes
})
test('add a single item', async done => {
expect.assertions(1)
// ARRANGE
const todo = await new ToDo() // DB runs in-memory if no name supplied
// ACT
await todo.add('bread', 3)
const count = await todo.countItems()
// ASSERT
expect(count).toBe(1)
done()
})

test('qty must be a number', async done => {
expect.assertions(1)
const todo = await new ToDo()
await expect( todo.add('bread', 'three') ).rejects.toEqual( Error('the quantity must be a number') )
done()
})

test('duplicates should increase qty', async done => {
expect.assertions(2)
// ARRANGE
const todo = await new ToDo()
// ACT
await todo.add('bread', 4)
await todo.add('bread', 2)
const data = await todo.getAll()
const qty = data[0].qty
// ASSERT (note there are two assertions as stated on line 42)
expect(data).toHaveLength(1)
expect(qty).toEqual(6)
done()
})

test('item cannot be empty', async done => {
expect.assertions(1)
const todo = await new ToDo()
await expect( todo.add('', 1) ).rejects.toEqual( Error('item cannot be empty') )
done()
})

test('qty cannot be empty', async done => {
expect.assertions(1)
const todo = await new ToDo()
await expect( todo.add('bread', '') ).rejects.toEqual( Error('qty cannot be empty') )
done()
})

})

describe('delete()', () => {
beforeEach( async() => {
this.todo = await new ToDo()
await this.todo.add('bread', 5)
})

test('id cannot be undefined', async done => {
expect.assertions(1)
await expect( this.todo.delete() ).rejects.toEqual( Error('id cannot be undefined') )
done()
})

test('id must be a number', async done => {
expect.assertions(1)
await expect( this.todo.delete('a') ).rejects.toEqual( Error('id must be a number') )
done()
})

test('id has to exist', async done => {
expect.assertions(1)
await expect( this.todo.delete(2) ).rejects.toEqual( Error('id has to exist') )
done()
})

test('delete an item', async done => {
expect.assertions(2)
await this.todo.delete(1)
expect(await this.todo.countItems()).toEqual(0)
expect(await this.todo.getAll()).toEqual([])
done()
})
})

describe('getAll()', () => {
beforeEach( async() => {
this.todo = await new ToDo()
})

test('getAll() with no items', async done => {
expect.assertions(1)
expect(await this.todo.getAll()).toEqual([])
done()
})

test('getAll() with a single item', async done => {
expect.assertions(1)
await this.todo.add('bread', 2)
expect(await this.todo.getAll()).toEqual([{"item":"bread", "qty":2, "id":1}])
done()
})

test('getAll() with two items', async done => {
expect.assertions(1)
await this.todo.add('bread', 2)
await this.todo.add('ham', 3)
expect(await this.todo.getAll()).toEqual([{"item":"bread", "qty":2, "id":1}, {"item":"ham", "qty":3, "id":2}])
done()
})
})

describe('clear()', () => {
beforeEach( async() => {
this.todo = await new ToDo()
})

test('clear() with no items', async done => {
expect.assertions(2)
await this.todo.clear()
expect(await this.todo.countItems()).toEqual(0)
expect(await this.todo.getAll()).toEqual([])
done()
})

test('clear() with a single item', async done => {
expect.assertions(2)
await this.todo.add('bread', 2)
await this.todo.clear()
expect(await this.todo.countItems()).toEqual(0)
expect(await this.todo.getAll()).toEqual([])
done()
})

test('clear() with two items', async done => {
expect.assertions(2)
await this.todo.add('bread', 2)
await this.todo.add('ham', 3)
await this.todo.clear()
expect(await this.todo.countItems()).toEqual(0)
expect(await this.todo.getAll()).toEqual([])
done()
})
})
@@ -0,0 +1,43 @@

'use strict'

let data = []

module.exports.clear = () => {
data = []
}

module.exports.add = (item, qty) => {
if (item === '') throw new Error('item cannot be empty')
if (qty === '') throw new Error('qty cannot be empty')
qty = Number(qty)
if(isNaN(qty)) throw new Error('qty must be a number')
let flag = false
for(const index in data) {
if (data[index].item === item) {
data[index].qty += qty
flag = true
}
}
if(flag === false) data.push({item: item, qty: qty})
}

module.exports.getAll = () => {
for(const key in data) data[key].key = Number(key)
return data
}

module.exports.delete = key => {
console.log(`delete key ${key}`)
if (key === undefined) throw new Error('key cannot be undefined')
key = Number(key)
if (isNaN(key)) throw new Error('key must be a number')
try {
this.getAll()[key].key // throws error unless the item with the target key exists
data.splice(key, 1)
} catch {
throw new Error('key has to exist')
}
}

module.exports.countItems = () => data.length

0 comments on commit 276525a

Please sign in to comment.