├── .babelrc ├── .coveralls.yml ├── .dockerignore ├── .editorconfig ├── .env ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── SUMMARY.md ├── app.js ├── bdd ├── exercises │ ├── findIndex.js │ ├── findInput.js │ └── zip.js ├── instructions.md ├── program.test.js └── solution │ └── program.test.js ├── book.json ├── config └── config.js ├── constants └── constants.js ├── crudOperations └── index.js ├── docker-compose.yml ├── docs ├── bdd │ └── bdd.md ├── e2e │ └── e2e.md ├── fixtures │ └── fixtures.md ├── integration │ └── integration.md ├── mocking │ └── mocking.md ├── property │ └── property.md ├── tdd │ └── tdd.md └── unit │ └── unit.md ├── end-to-end-tests ├── instructions.md ├── nightwatch.conf.js ├── nightwatch.runner.js ├── nightwatch │ └── codeCraftsmanshipSaturdays.js ├── page-objects │ └── codeCraftsmanshipSaturdaysPage.js └── solution │ └── nightwatch │ ├── nightwatch.solution.js │ └── nightwatch.solution.page.objects.js ├── gulpfile.js ├── icomoon ├── PNG │ ├── arrow-down-left.png │ ├── arrow-down-right.png │ ├── arrow-down6.png │ ├── arrow-left.png │ ├── arrow-left2.png │ ├── arrow-left3.png │ ├── arrow-left4.png │ ├── arrow-left5.png │ ├── arrow-left6.png │ ├── arrow-right3.png │ ├── arrow-right5.png │ ├── arrow-right6.png │ ├── arrow-up-left.png │ ├── arrow-up-right.png │ ├── arrow-up2.png │ ├── arrow-up3.png │ ├── arrow-up4.png │ ├── arrow-up5.png │ ├── arrow-up6.png │ ├── bin.png │ ├── bin2.png │ ├── box-add.png │ ├── bubble-dots2.png │ ├── bubble7.png │ ├── cart.png │ ├── cart4.png │ ├── checkbox-checked2.png │ ├── checkmark.png │ ├── circle-down2.png │ ├── circle-up2.png │ ├── cog.png │ ├── cog7.png │ ├── credit-card.png │ ├── cross.png │ ├── cross2.png │ ├── cross3.png │ ├── enlarge2.png │ ├── enlarge7.png │ ├── facebook2.png │ ├── file-check2.png │ ├── folder-plus4.png │ ├── folder-remove.png │ ├── folder3.png │ ├── google-plus2.png │ ├── home.png │ ├── home4.png │ ├── lock2.png │ ├── pencil.png │ ├── pencil2.png │ ├── play.png │ ├── play2.png │ ├── plus3.png │ ├── rss2.png │ ├── shrink7.png │ ├── skype.png │ ├── spinner10.png │ ├── spinner3.png │ ├── spinner7.png │ ├── spinner9.png │ ├── stack3.png │ ├── tumblr2.png │ ├── twitter.png │ ├── unlocked2.png │ ├── user3.png │ ├── user4.png │ ├── user5.png │ └── user6.png ├── Read Me.txt ├── SVG │ ├── arrow-down-left.svg │ ├── arrow-down-right.svg │ ├── arrow-down6.svg │ ├── arrow-left.svg │ ├── arrow-left2.svg │ ├── arrow-left3.svg │ ├── arrow-left4.svg │ ├── arrow-left5.svg │ ├── arrow-left6.svg │ ├── arrow-right3.svg │ ├── arrow-right5.svg │ ├── arrow-right6.svg │ ├── arrow-up-left.svg │ ├── arrow-up-right.svg │ ├── arrow-up2.svg │ ├── arrow-up3.svg │ ├── arrow-up4.svg │ ├── arrow-up5.svg │ ├── arrow-up6.svg │ ├── bin.svg │ ├── bin2.svg │ ├── box-add.svg │ ├── bubble-dots2.svg │ ├── bubble7.svg │ ├── cart.svg │ ├── cart4.svg │ ├── checkbox-checked2.svg │ ├── checkmark.svg │ ├── circle-down2.svg │ ├── circle-up2.svg │ ├── cog.svg │ ├── cog7.svg │ ├── credit-card.svg │ ├── cross.svg │ ├── cross2.svg │ ├── cross3.svg │ ├── enlarge2.svg │ ├── enlarge7.svg │ ├── facebook2.svg │ ├── file-check2.svg │ ├── folder-plus4.svg │ ├── folder-remove.svg │ ├── folder3.svg │ ├── google-plus2.svg │ ├── home.svg │ ├── home4.svg │ ├── lock2.svg │ ├── pencil.svg │ ├── pencil2.svg │ ├── play.svg │ ├── play2.svg │ ├── plus3.svg │ ├── rss2.svg │ ├── shrink7.svg │ ├── skype.svg │ ├── spinner10.svg │ ├── spinner3.svg │ ├── spinner7.svg │ ├── spinner9.svg │ ├── stack3.svg │ ├── tumblr2.svg │ ├── twitter.svg │ ├── unlocked2.svg │ ├── user3.svg │ ├── user4.svg │ ├── user5.svg │ └── user6.svg ├── demo-external-svg.html ├── demo-files │ └── demo.css ├── demo.html ├── selection.json ├── style.css ├── svgxuse.js └── symbol-defs.svg ├── images └── favicon.ico ├── integration-tests ├── instructions.md ├── program.test.js └── solution │ └── program.test.js ├── mocks-stubs-spies ├── instructions.md ├── program.tape.test.js ├── program.test.js └── solution │ ├── node-nock │ └── program.test.js │ └── sinon │ ├── program.tape.test.js │ └── program.test.js ├── models ├── crudOperations.js ├── db.js ├── userModels.js └── users.js ├── package-lock.json ├── package.json ├── property-tests └── program.test.js ├── routes └── index.js ├── static ├── build │ ├── react-bootstrap.min.js │ ├── react-dom.min.js │ ├── react.min.js │ ├── software-testing.css │ ├── software-testing.css.map │ └── symbol-defs.svg ├── js │ ├── actions │ │ └── index.js │ ├── components │ │ ├── App.jsx │ │ ├── CodeCraftsmanship.jsx │ │ ├── Main.jsx │ │ ├── UserDetails.jsx │ │ └── Users.jsx │ ├── constants │ │ └── index.js │ ├── data │ │ └── index.js │ ├── reducers │ │ ├── index.js │ │ ├── userDetailInformation.js │ │ └── users.js │ ├── store │ │ └── index.js │ └── utils │ │ └── ajax.js └── scss │ ├── software-testing.scss │ ├── userDetail.scss │ └── users.scss ├── tdd ├── instructions.md ├── solution │ ├── program.js │ └── tdd-cycle │ │ ├── cycle1 │ │ ├── program.js │ │ └── program.test.js │ │ ├── cycle2 │ │ ├── program.js │ │ └── program.test.js │ │ ├── cycle3 │ │ ├── program.js │ │ └── program.test.js │ │ └── cyclefinal │ │ ├── program.js │ │ └── program.test.js └── tdd-cycle │ ├── cycle1 │ ├── program.js │ └── program.test.js │ ├── cycle2 │ ├── program.js │ └── program.test.js │ ├── cycle3 │ ├── program.js │ └── program.test.js │ └── cyclefinal │ ├── program.js │ └── program.test.js ├── test-fixtures ├── program.test.js └── solution │ └── program.test.js ├── unit-test ├── exercises │ ├── concatAll │ │ └── concatAll.js │ ├── concatMap │ │ └── concatMap.js │ ├── filter │ │ └── filter.js │ └── map │ │ └── map.js ├── instructions.md ├── program.test.js └── solution │ └── program.test.js ├── users └── users.js ├── views ├── error.hbs └── index.hbs └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react", "stage-0", "react-hmre"], 3 | "plugins": ["add-module-exports"] 4 | } -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: CPfB9RcpnKUcOHwgfdWqC1ODTg5LVTYZ5 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | indent_size = 4 15 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | PORT=3000 -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | end-to-end-tests/nightwatch.conf.js -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | extends: 'eslint:recommended', 8 | // add your custom rules here 9 | 'rules': { 10 | // allow debugger during development 11 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 12 | 'no-extra-boolean-cast': 'off', 13 | 'no-console': 'warn', 14 | 'no-case-declarations': 'warn', 15 | 'no-cond-assign': 'warn', 16 | 'brace-style': 'error', 17 | 'comma-dangle': 'error' 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.vscode 3 | /.DS_Store 4 | /reports/nightwatch 5 | /bin 6 | /dist 7 | /static/build 8 | /selenium-debug.log 9 | /npm-debug.log 10 | /coverage 11 | /.nyc_output 12 | /_book 13 | /reports 14 | /ca 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | language: node_js 4 | node_js: 5 | - 7 6 | env: 7 | DOCKER_COMPOSE_VERSION: 1.11.2 8 | addons: 9 | sources: 10 | - google-chrome 11 | apt: 12 | packages: 13 | - oracle-java8-installer 14 | - oracle-java8-set-default 15 | - google-chrome-stable 16 | - couchdb 17 | before_script: 18 | - export CHROME_BIN=chromium-browser 19 | - "export DISPLAY=:99.0" 20 | - "sh -e /etc/init.d/xvfb start" 21 | - sleep 3 # give xvfb some time to start 22 | after_success: 23 | - npm run build 24 | - npm run coveralls 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mhart/alpine-node:6.10.0 2 | 3 | LABEL Marcel Belmont "marcelbelmont@gmail.com" 4 | 5 | ENV appDir /var/www/app 6 | 7 | # Run updates and install deps 8 | RUN apk add --no-cache make gcc g++ python 9 | 10 | # Set the work directory 11 | RUN mkdir -p /var/www/app 12 | WORKDIR ${appDir} 13 | 14 | ADD package.json ./ 15 | RUN npm install 16 | 17 | # Install gulp so we can run our application 18 | RUN npm i -g gulp nyc tape rimraf 19 | 20 | # Add application files 21 | ADD . /var/www/app 22 | 23 | # Define mountable directories. 24 | VOLUME ["/usr/local/var/lib/couchdb"] 25 | 26 | CMD ["npm", "run", "dev"] 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Code Craftsmanship Saturdays 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Software Testing Concepts 2 | 3 | * [README](/README.md) 4 | * [Unit Testing](/docs/unit/unit.md) 5 | * [Integration Testing](/docs/integration/integration.md) 6 | * [TDD](/docs/tdd/tdd.md) 7 | * [BDD](/docs/bdd/bdd.md) 8 | * [End to End Testing](/docs/e2e/e2e.md) 9 | * [Mocking](/docs/mocking/mocking.md) 10 | * [Test Fixtures](/docs/fixtures/fixtures.md) 11 | * [Property Based Testing](/docs/property/property.md) 12 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | const {join} = require('path'); 5 | const favicon = require('serve-favicon'); 6 | const logger = require('morgan'); 7 | const cookieParser = require('cookie-parser'); 8 | const bodyParser = require('body-parser'); 9 | const winston = require('winston'); 10 | 11 | // Load Environment Variables 12 | require(join(__dirname, 'config/config'))["dotEnv"]; 13 | 14 | const routes = require(join(__dirname, 'routes/index')); 15 | const users = require(join(__dirname, 'users/users')); 16 | const crud = require(join(__dirname, 'crudOperations/index')); 17 | const app = express(); 18 | 19 | // view engine setup 20 | app.set('views', join(__dirname, 'views')); 21 | app.set('view engine', 'hbs'); 22 | 23 | app.use(favicon(join(__dirname, 'static/build', 'favicon.ico'))); 24 | app.use(logger('dev')); 25 | 26 | app.use(bodyParser.json()); 27 | app.use(bodyParser.urlencoded({ extended: false })); 28 | app.use(cookieParser()); 29 | 30 | app.use(express.static(join(__dirname, 'static'))); 31 | 32 | app.use('/', routes); 33 | app.use('/api/v1/users', users); 34 | app.use('/api/v1/couch', crud); 35 | 36 | /** 37 | * catch 404 and forward to error handler 38 | */ 39 | app.use((req, res, next) => { 40 | let err = new Error('Not Found'); 41 | err.status = 404; 42 | next(err); 43 | }); 44 | 45 | /** 46 | * development error handler 47 | * will print stacktrace 48 | */ 49 | if (app.get('env') === 'development') { 50 | app.use((err, req, res, next) => { 51 | res.status(err.status || 500); 52 | res.render('error', { 53 | message: err.message, 54 | error: err 55 | }); 56 | }); 57 | } 58 | 59 | /** 60 | * production error handler 61 | * no stacktraces leaked to user 62 | * */ 63 | app.use((err, req, res, next) => { 64 | res.status(err.status || 500); 65 | res.render('error', { 66 | message: err.message, 67 | error: {} 68 | }); 69 | }); 70 | 71 | 72 | module.exports = app; -------------------------------------------------------------------------------- /bdd/exercises/findIndex.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Function finds index value for array condition. 3 | */ 4 | Array.prototype.findIdx = function(fn) { 5 | return this.indexOf(this.filter(val => fn(val))[0]); 6 | }; -------------------------------------------------------------------------------- /bdd/exercises/findInput.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Function finds value for array. 3 | */ 4 | Array.prototype.findInput = function(fn) { 5 | return this.filter(val => fn(val))[0]; 6 | }; -------------------------------------------------------------------------------- /bdd/exercises/zip.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Function groups sets 2-dimensional arrays like this: 3 | * [ [1,2,3], ["one","two","three"], [true,false,true] ] 4 | * => [ [1,"one",true], [2,"two",false], [3,"three",true] ] 5 | */ 6 | Array.prototype.zip = function() { 7 | return this.map((value, index, arr) => { 8 | return arr.map((val, idx, array) => { 9 | return val[index]; 10 | }); 11 | }); 12 | }; 13 | 14 | // Here is how it is built step by step 15 | // => Remember map always returns an array 16 | // => [1], [2], [3] 17 | // => [1, "one"], [2, "two"], [3, "three"] 18 | // => [1, "one", true], [2, "two", false], [3, "three", true] -------------------------------------------------------------------------------- /bdd/instructions.md: -------------------------------------------------------------------------------- 1 | ## Behavior-Driven Development (BDD) 2 | 3 | To view lecture notes for this course, please consult the [github-pages](https://jbelmont.github.io/software-testing). 4 | 5 | ## Definition of BDD via Wikipedia 6 | BDD (behavior-driven development) combines practices from TDD and from ATDD.[27] It includes the practice of writing tests first, but focuses on tests which describe behavior, rather than tests which test a unit of implementation. Tools such as Mspec and Specflow provide a syntax which allow non-programmers to define the behaviors which developers can then translate into automated tests. Behavior-driven development combines the general techniques and principles of TDD with ideas from domain-driven design and object-oriented analysis and design to provide software development and management teams with shared tools and a shared process to collaborate on software development. 7 | 8 | ## Another Look at what BDD is via Josh Davis [Blog](http://joshldavis.com/2013/05/27/difference-between-tdd-and-bdd/) 9 | The main difference is just the wording. BDD uses a more verbose style so that it can be read almost like a sentence. 10 | 11 | In contrast to TDD, BDD is when we write behavior & specification that then drives our software development. 12 | The ability to read your tests like a sentence is a cognitive shift in how you will think about your tests. The argument is that if you can read your tests fluidly, you will naturally write better and more comprehensive tests. 13 | 14 | Open program.test.js and go to each TODO block. 15 | 16 | ### 1. Unit Test the findIdx Function: 17 | ```javascript 18 | it('Unit test the Array.prototype.findIdx function', done => { 19 | const numbers = [1,2,3,4,5]; 20 | const expected = 2; 21 | expect(numbers.findIdx(val => val === 3)).to.eql(expected); 22 | 23 | const names = [ 24 | { 25 | name: 'Marcel' 26 | }, 27 | { 28 | name: 'Leo' 29 | }, 30 | { 31 | name: 'Dave' 32 | } 33 | ]; 34 | const IDX = 1; 35 | // TODO add assertions here: 36 | done(); 37 | }); 38 | ``` 39 | 40 | ##### Read the chai assertion [Assertions](http://chaijs.com/api/assert) 41 | ##### Read the BDD Styles for Expect and Should [Styles](http://chaijs.com/guide/styles) 42 | 43 | 44 | *For Unit tests I usually create 2 variables one named actual and another named expect but this is strictly up to you.* 45 | 46 | _Expect example_ 47 | ```javascript 48 | expect(someTest).to.eql(ThisAssertion); 49 | ``` 50 | 51 | _Should example_ 52 | ```javascript 53 | foo.should.equal('bar'); 54 | ``` 55 | 56 | The `Array.prototype.findIdx` function behaves in the following manner 57 | ```javascript 58 | [{ name: 'Marcel' },{ name: 'Leo' },{ name: 'Dave' }].findIdx(function(value) { 59 | return value["name"] === "Leo"; 60 | }); 61 | 62 | // outputs => 1 63 | ``` 64 | 65 | ### 2. Unit test the Array.prototype.findInput Function 66 | 67 | Write a BDD Style test using the following structure in program.test.js 68 | ```javascript 69 | it('Unit test the Array.prototype.findInput function', function(done) { 70 | // TODO: Finish the test here. 71 | done(); 72 | }); 73 | ``` 74 | 75 | The `Array.prototype.findInput` function behaves in the following manner 76 | ```javascript 77 | [{ name: 'Marcel' },{ name: 'Leo' },{ name: 'Dave' }].findInput(function(value) { 78 | return value["name"] === "Leo"; 79 | }); 80 | 81 | // outputs => { name: 'Leo' } 82 | ``` 83 | 84 | ### 3. Unit test the Array.prototype.zip Function 85 | 86 | Write a BDD Style Test in the TODO block in program.test.js 87 | 88 | The `Array.prototype.zip` function behaves in the following manner 89 | ```javascript 90 | [ 91 | [1, 2, 3], 92 | ["one", "two", "three"], 93 | [true, false, true] 94 | ].zip(); 95 | 96 | // outputs => 97 | [ 98 | [1, "one", true], 99 | [2, "two", false], 100 | [3, "three", true] 101 | ] 102 | ``` 103 | -------------------------------------------------------------------------------- /bdd/program.test.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const expect = chai.expect; 3 | const should = chai.should(); 4 | 5 | require('./exercises/findIndex'); 6 | require('./exercises/findInput'); 7 | require('./exercises/zip'); 8 | 9 | describe('', function() { 10 | 11 | // TODO: Test the findIdx function by using either chai expect or should assertions 12 | it('Unit test the Array.prototype.findIdx function', function(done) { 13 | done(); 14 | }); 15 | 16 | // TODO: Write a Unit Test for Array.prototype.find function using BDD style assertions like before. 17 | 18 | // TODO: Write a Unit Test for Array.prototype.zip function using BDD style assertions 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /bdd/solution/program.test.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const expect = chai.expect; 3 | const should = chai.should(); 4 | 5 | require('../exercises/findIndex'); 6 | require('../exercises/findInput'); 7 | require('../exercises/zip'); 8 | 9 | describe('Practice Unit testing with BDD style library such as Mocha', () => { 10 | 11 | it('Unit test the Array.prototype.findIdx function', done => { 12 | const numbers = [1,2,3,4,5]; 13 | const expected = 2; 14 | expect(numbers.findIdx(val => val === 3)).to.eql(expected); 15 | 16 | const names = [ 17 | { 18 | name: 'Marcel' 19 | }, 20 | { 21 | name: 'Leo' 22 | }, 23 | { 24 | name: 'Dave' 25 | } 26 | ]; 27 | const IDX = 1; 28 | expect(names.findIdx(val => val["name"] === 'Leo')).to.eql(IDX); 29 | expect(names.findIdx(val => val["name"] === 'Allan') > -1).to.not.be.ok; 30 | done(); 31 | }); 32 | 33 | it('Unit test the Array.prototype.findInput function', done => { 34 | const information = [ 35 | { 36 | city: 'Cincinnati', 37 | population: 2500000 38 | }, 39 | { 40 | city: 'Phoenix', 41 | timezone: 'Mountain Time Zone' 42 | }, 43 | { 44 | city: 'Raleigh', 45 | state: 'NC' 46 | } 47 | ]; 48 | const expected = { 49 | city: 'Raleigh', 50 | state: 'NC' 51 | }; 52 | information.findInput(info => info["city"] === 'Raleigh').should.eql(expected); 53 | should.equal(information.findInput(info => info["population"] < 200000), undefined); 54 | done(); 55 | }); 56 | 57 | it('Unit test the Array.prototype.zip function', done => { 58 | const nestedArray = [ 59 | [1, 2, 3], 60 | ["one", "two", "three"], 61 | [true, false, true] 62 | ]; 63 | 64 | const expected = [ 65 | [1, "one", true], 66 | [2, "two", false], 67 | [3, "three", true] 68 | ]; 69 | 70 | expect(nestedArray.zip()).to.be.deep.equal(expected); 71 | done(); 72 | }); 73 | 74 | }); -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "include-codeblock", 4 | "advanced-emoji", 5 | "prism", 6 | "highlight", 7 | "copy-code-button" 8 | ] 9 | } -------------------------------------------------------------------------------- /config/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dotEnv: require('dotenv').config() 3 | }; -------------------------------------------------------------------------------- /constants/constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | statusCodes: { 3 | ok: 200, 4 | delete: 204, 5 | create: 201, 6 | badRequest: 400, 7 | unAuthorized: 401, 8 | forbidden: 403, 9 | conflict: 409, 10 | gone: 410, 11 | internalServerError: 500 12 | } 13 | }; -------------------------------------------------------------------------------- /crudOperations/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | const router = express.Router(); 5 | const path = require('path'); 6 | const winston = require('winston'); 7 | 8 | const crud = require('../models/crudOperations'); 9 | const statusCodes = require('../constants/constants')["statusCodes"]; 10 | 11 | router.get('/retrieveDocument/:name', (req, res, next) => { 12 | const name = req.params.name; 13 | if (name) { 14 | const ok = statusCodes["ok"]; 15 | const serverError = statusCodes["internalServerError"]; 16 | return crud.retrieveDocument({ 17 | dbName: 'softwaretesting', 18 | name: name 19 | }) 20 | .then(body => { 21 | res.status(ok).send(body); 22 | }) 23 | .catch(err => { 24 | res.status(serverError).send(err); 25 | }); 26 | } 27 | else { 28 | const badRequest = statusCodes["badRequest"]; 29 | res.status(badRequest).send({ 30 | err: new Error(badRequest), 31 | message: 'Missing name.' 32 | }); 33 | } 34 | }); 35 | 36 | router.post('/insertDocument', (req, res, next) => { 37 | const { 38 | name, 39 | document 40 | } = req.body; 41 | if (name && document) { 42 | const created = statusCodes["create"]; 43 | const conflict = statusCodes["conflict"]; 44 | return crud.insertDocument({ 45 | dbName: 'softwaretesting', 46 | name: name, 47 | body: document 48 | }) 49 | .then(body => { 50 | res.status(created).send(body); 51 | }) 52 | .catch(err => { 53 | res.status(conflict).send(err); 54 | }); 55 | } 56 | else { 57 | const badRequest = statusCodes["badRequest"]; 58 | res.status(badRequest).send({ 59 | err: new Error(badRequest), 60 | message: 'Missing name/document. Please pass in an object with a name and document property.' 61 | }); 62 | } 63 | }); 64 | 65 | router.post('/insertDocument/:name', (req, res, next) => { 66 | const { 67 | name 68 | } = req.params; 69 | const { 70 | document 71 | } = req.body; 72 | if (name && document) { 73 | const created = statusCodes["create"]; 74 | const conflict = statusCodes["conflict"]; 75 | return crud.insertDocument({ 76 | dbName: 'softwaretesting', 77 | name: name, 78 | body: document 79 | }) 80 | .then(body => { 81 | res.status(created).send(body); 82 | }) 83 | .catch(err => { 84 | res.status(conflict).send(err); 85 | }); 86 | } else { 87 | const badRequest = statusCodes["badRequest"]; 88 | res.status(badRequest).send({ 89 | err: new Error(badRequest), 90 | message: 'Missing name/document.' 91 | }); 92 | } 93 | }); 94 | 95 | router.delete('/deleteDocument', (req, res, next) => { 96 | const { 97 | name 98 | } = req.body; 99 | if (name) { 100 | const removed = statusCodes["delete"]; 101 | const gone = statusCodes["gone"]; 102 | return crud.deleteDocument({ 103 | dbName: 'softwaretesting', 104 | name 105 | }) 106 | .then(body => { 107 | res.status(removed).send(body); 108 | }) 109 | .catch(err => { 110 | res.status(gone).send(err); 111 | }); 112 | } else { 113 | const badRequest = statusCodes["badRequest"]; 114 | res.status(badRequest).send({ 115 | err: new Error(badRequest), 116 | message: 'Missing name. You need to pass in a name property.' 117 | }); 118 | } 119 | }); 120 | 121 | router.delete('/deleteDocument/:name', (req, res, next) => { 122 | const { 123 | name 124 | } = req.params; 125 | if (name) { 126 | const removed = statusCodes["delete"]; 127 | const gone = statusCodes["gone"]; 128 | return crud.deleteDocument({ 129 | dbName: 'softwaretesting', 130 | name 131 | }) 132 | .then(body => { 133 | res.status(removed).send(body); 134 | }) 135 | .catch(err => { 136 | res.status(gone).send(err); 137 | }); 138 | } else { 139 | const badRequest = statusCodes["badRequest"]; 140 | res.status(badRequest).send({ 141 | err: new Error(badRequest), 142 | message: 'Missing name. You need to pass in a name property.' 143 | }); 144 | } 145 | }); 146 | 147 | module.exports = router; 148 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | web: 4 | build: . 5 | ports: 6 | - "3000:3000" 7 | test: 8 | image: mhart/alpine-node:7.9.0 9 | working_dir: /var/www/app 10 | volumes: 11 | - .:/var/www/app 12 | environment: 13 | NODE_ENV: testing 14 | command: 15 | /bin/sh -c "./node_modules/.bin/nyc ./node_modules/.bin/tape tdd/solution/tdd-cycle/cyclefinal/program.test.js unit-test/solution/program.test.js" 16 | db: 17 | image: couchdb:1.6.1 18 | environment: 19 | COUCHDB_USER: admin 20 | COUCHDB_PASSWORD: password 21 | ports: 22 | - "5984:5984" 23 | e2e: 24 | build: . 25 | command: 26 | /bin/sh -c "node end-to-end-tests/nightwatch.runner.js" 27 | -------------------------------------------------------------------------------- /docs/bdd/bdd.md: -------------------------------------------------------------------------------- 1 | ## Behavior-Driven Development (BDD) 2 | 3 | To view lecture notes for this course, please consult the [github-pages](https://jbelmont.github.io/software-testing). 4 | 5 | ## Definition of BDD via Wikipedia 6 | BDD (behavior-driven development) combines practices from TDD and from ATDD.[27] It includes the practice of writing tests first, but focuses on tests which describe behavior, rather than tests which test a unit of implementation. Tools such as Mspec and Specflow provide a syntax which allow non-programmers to define the behaviors which developers can then translate into automated tests. Behavior-driven development combines the general techniques and principles of TDD with ideas from domain-driven design and object-oriented analysis and design to provide software development and management teams with shared tools and a shared process to collaborate on software development. 7 | 8 | ## Another Look at what BDD is via Josh Davis [Blog](http://joshldavis.com/2013/05/27/difference-between-tdd-and-bdd/) 9 | The main difference is just the wording. BDD uses a more verbose style so that it can be read almost like a sentence. 10 | 11 | In contrast to TDD, BDD is when we write behavior & specification that then drives our software development. 12 | The ability to read your tests like a sentence is a cognitive shift in how you will think about your tests. The argument is that if you can read your tests fluidly, you will naturally write better and more comprehensive tests. 13 | 14 | Instructions for BDD Exercises: 15 | 1. Go to bdd folder `cd bdd` 16 | 2. Open program.test.js and go to each TODO block. 17 | 3. Complete each todo block by adding bdd style unit test. 18 | 4. In order to complete the exercises run the script `npm run bdd:test` 19 | 20 | ### 1. Unit Test the findIdx Function: 21 | ```javascript 22 | it('Unit test the Array.prototype.findIdx function', done => { 23 | const numbers = [1,2,3,4,5]; 24 | const expected = 2; 25 | expect(numbers.findIdx(val => val === 3)).to.eql(expected); 26 | 27 | const names = [ 28 | { 29 | name: 'Marcel' 30 | }, 31 | { 32 | name: 'Leo' 33 | }, 34 | { 35 | name: 'Dave' 36 | } 37 | ]; 38 | const IDX = 1; 39 | // TODO add assertions here: 40 | done(); 41 | }); 42 | ``` 43 | 44 | ##### Read the chai assertion [Assertions](http://chaijs.com/api/assert) 45 | ##### Read the BDD Styles for Expect and Should [Styles](http://chaijs.com/guide/styles) 46 | 47 | 48 | *For Unit tests I usually create 2 variables one named actual and another named expect but this is strictly up to you.* 49 | 50 | _Expect example_ 51 | ```javascript 52 | expect(someTest).to.eql(ThisAssertion); 53 | ``` 54 | 55 | _Should example_ 56 | ```javascript 57 | foo.should.equal('bar'); 58 | ``` 59 | 60 | The `Array.prototype.findIdx` function behaves in the following manner 61 | ```javascript 62 | [{ name: 'Marcel' },{ name: 'Leo' },{ name: 'Dave' }].findIdx(function(value) { 63 | return value["name"] === "Leo"; 64 | }); 65 | 66 | // outputs => 1 67 | ``` 68 | 69 | ### 2. Unit test the Array.prototype.findInput Function 70 | 71 | Write a BDD Style test using the following structure in program.test.js 72 | ```javascript 73 | it('Unit test the Array.prototype.findInput function', function(done) { 74 | // TODO: Finish the test here. 75 | done(); 76 | }); 77 | ``` 78 | 79 | The `Array.prototype.findInput` function behaves in the following manner 80 | ```javascript 81 | [{ name: 'Marcel' },{ name: 'Leo' },{ name: 'Dave' }].findInput(function(value) { 82 | return value["name"] === "Leo"; 83 | }); 84 | 85 | // outputs => { name: 'Leo' } 86 | ``` 87 | 88 | ### 3. Unit test the Array.prototype.zip Function 89 | 90 | Write a BDD Style Test in the TODO block in program.test.js 91 | 92 | The `Array.prototype.zip` function behaves in the following manner 93 | ```javascript 94 | [ 95 | [1, 2, 3], 96 | ["one", "two", "three"], 97 | [true, false, true] 98 | ].zip(); 99 | 100 | // outputs => 101 | [ 102 | [1, "one", true], 103 | [2, "two", false], 104 | [3, "three", true] 105 | ] 106 | ``` 107 | -------------------------------------------------------------------------------- /docs/fixtures/fixtures.md: -------------------------------------------------------------------------------- 1 | ## Test Fixtures 2 | 3 | [Test Fixtures](https://en.wikipedia.org/wiki/Test_fixture) 4 | 5 | * * A test fixture is a fixed state of a set of objects used as a baseline for running tests. 6 | * A test fixture is something used to consistently test some item, device, or piece of software. 7 | * Test fixtures can be found when testing electronics, software and physical devices. 8 | * A software test fixture sets up the system for the testing process by providing the initialization code. 9 | * In turn satisfying whatever preconditions there may be. 10 | An example could be loading up a database with known parameters from a customer site before running your test. 11 | * Ruby on Rails web framework uses YAML to initialize a database before running a test. 12 | * This allows for tests to be repeatable, which is one of the key features of an effective test framework 13 | 14 | Advantages of Test Fixtures: 15 | 16 | * Test Fixtures allow for tests to be repeatable since you start with the same setup every time. 17 | * Test Fixtures eases test code design by allowing the developer to separate methods into different functions and reuse each function for other tests. 18 | * Preconfigures tests into a known state at start instead of working from a previous test run. 19 | 20 | * The purpose of a test fixture is to ensure that there is a well known and fixed environment in which tests are run so that results are repeatable. 21 | 22 | Examples of Test Fixtures: 23 | 24 | 1. Preparation of input data and setup/creation of fake or mock objects 25 | 2. Loading a database with a specific, known set of data 26 | 3. Copying a specific known set of files creating a test fixture will create a set of objects initialized to certain states. 27 | 28 | Open up `test-fixtures` directory and add an integration using supertest to retrieve the seeded document 29 | -------------------------------------------------------------------------------- /docs/integration/integration.md: -------------------------------------------------------------------------------- 1 | ## Integration Tests 2 | 3 | To view lecture notes for this course, please consult the [github-pages](https://jbelmont.github.io/software-testing). 4 | 5 | ## Distinction between Unit Tests 6 | *Introducing dependencies on external modules or data also turns unit tests into integration tests.* 7 | 8 | *If one module misbehaves in a chain of interrelated modules, it is not so immediately clear where to look for the cause of the failure.* 9 | *When code under development relies on a database, a web service, or any other external process or service,* 10 | *enforcing a unit-testable separation is also an opportunity and a driving force to design more modular, more testable and more reusable code.* 11 | 12 | [Distinction between Unit Tests](https://en.wikipedia.org/wiki/Test-driven_development) 13 | 14 | ## Starting instructions 15 | * Open 2 terminal prompts 16 | * run the command `couchdb` 17 | * `cd integration-tests` 18 | ### II. Using text editor of your choice open program.test.js and complete each TODO block. 19 | 20 | ### 1. Finish Integration test for the /api/v1/users/badMofos endpoint: 21 | 22 | #### Inspect the payload with the following curl command 23 | ```sh 24 | curl -X GET -H "Accept: application/json" -H "Cache-Control: no-cache" "http://localhost:3000/api/v1/users/badMofos" 25 | ``` 26 | #### Use the telnet command and paste in the following commands to your terminal and hit enter. 27 | ```sh 28 | telnet localhost 3000 29 | ``` 30 | 31 | Paste this GET request into terminal that is expecting request and line feed and hit enter twice 32 | ```sh 33 | GET /api/v1/users/badMofos HTTP/1.1 34 | Host: localhost:3000 35 | Accept: application/json 36 | Cache-Control: no-cache 37 | Postman-Token: f3413251-c0de-69ac-99dd-992bcaaca3bd 38 | ``` 39 | 40 | #### Use a REST client such as Postman Chrome App or anything else. 41 | 42 | ##### Whichever way you use choose you get the following JSON payload 43 | ```json 44 | { 45 | "_id": "users", 46 | "_rev": "1-c9d988323eed080b054d6eb467abe4f9", 47 | "names": [ 48 | "John J Rambo", 49 | "Conan The Barbarian", 50 | "Billy Jack" 51 | ], 52 | "ranks": [ 53 | "One Bad Mofo", 54 | "Too Big of a dude", 55 | "Kicks too high for my taste" 56 | ] 57 | } 58 | ``` 59 | 60 | * Add assertion to expect function call in line 16 using the payload information. 61 | 62 | ### 2. Finish Integration Test for /api/v1/couch/insertDocument endpoint 63 | 64 | *The Super Test library api docs can be found here [SuperTest](https://visionmedia.github.io/superagent)* 65 | 66 | ####Hints to complete the exercise: 67 | *1. Use post method in supertest* 68 | *2. Use set method in supertest and pass in object with Accept and Content-Type headers.* 69 | *3. Use send method in supertest and pass in object with a name and document property.* 70 | *4. Make sure to call expect in supertest to do assertion and use previous get request as example.* 71 | *5. Make sure to end supertest call with end function call or the integration test won't finish.* 72 | *6. Check statuscode with SuperTest property [Status](https://visionmedia.github.io/superagent/#response-status)* 73 | *7. Use assertion methods from Tape [Asserts](http://www.node-tap.org/asserts)* 74 | *8. CouchDB api documentation [Docs](https://wiki.apache.org/couchdb/HTTP_Document_API)* 75 | *9. CouchDB driver for node.js (nano) that I am using [Docs](https://github.com/dscape/nano)* 76 | 77 | #### Making Rest Call with Curl for Post Request 78 | ```sh 79 | curl -X POST -H "Accept: application/json" -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{ 80 | "name": "some-document", 81 | "document": { 82 | "values": { 83 | "one": 1 84 | } 85 | } 86 | }' "http://localhost:3000/api/v1/couch/insertDocument" 87 | ``` 88 | 89 | ### 3. Write an integration test removing the newly created document to /api/v1/couch/deleteDocument/:name 90 | 91 | *The Super Test library api docs and rest methods can be found here [SuperTest](https://visionmedia.github.io/superagent/#request-basics)* 92 | 93 | #### Hints to complete the exercise: 94 | *1. Use appropriate supertest method to remove document.* 95 | *2. Make assertion with the returned status code (204) is usual status for DELETE request.* 96 | 97 | #### Making Rest Call with Curl for Delete Request 98 | ```sh 99 | curl -X DELETE -H "Content-Type: application/json" -d '{ 100 | "name": "spicegirls" 101 | }' "http://localhost:3000/api/v1/couch/deleteDocument" 102 | ``` 103 | -------------------------------------------------------------------------------- /docs/mocking/mocking.md: -------------------------------------------------------------------------------- 1 | ## Mocks 2 | 3 | To view lecture notes for this course, please consult the [github-pages](https://jbelmont.github.io/software-testing). 4 | 5 | #### Definition of Mocks via Wikipedia [Mocks](https://en.wikipedia.org/wiki/Mock_object) 6 | In object-oriented programming, mock objects (also can be a unit of work) are simulated objects that mimic the behavior of real objects in controlled ways. A programmer typically creates a mock object to test the behavior of some other object, in much the same way that a car designer uses a crash test dummy to simulate the dynamic behavior of a human in vehicle impacts. 7 | 8 | #### Reasons to use Mock Objects 9 | In a unit test, mock objects can simulate the behavior of complex, real objects and are therefore useful when a real object is impractical or impossible to incorporate into a unit test. If an actual object has any of the following characteristics, it may be useful to use a mock object in its place: 10 | the object supplies non-deterministic results (e.g. the current time or the current temperature); 11 | 12 | **The Object has states that are difficult to create or reproduce (e.g. a network error);** 13 | **The Object is slow (e.g. a complete database, which would have to be initialized before the test);** 14 | **The Object does not yet exist or may change behavior;** 15 | **The Object would have to include information and methods exclusively for testing purposes (and not for its actual task).** 16 | 17 | #### Sinon.js Mock via explanation [Sinon Mocks](http://sinonjs.org/docs/#mocks) 18 | Mocks (and mock expectations) are fake methods (like spies) with pre-programmed behavior (like stubs) as well as pre-programmed expectations. 19 | A mock will fail your test if it is not used as expected. 20 | 21 | Sinon Documentation discusses when not to use Mocks. 22 | Mocks come with built-in expectations that may fail your test. Thus, they enforce implementation details. 23 | **The rule of thumb is: if you wouldn’t add an assertion for some specific call, don’t mock it. Use a stub instead.** 24 | **In general you should never have more than one mock (possibly with several expectations) in a single test.** 25 | 26 | #### Sinon.js Stubs via explanation [Sinon Stubs](http://sinonjs.org/docs/#stubs) 27 | Test stubs are functions (spies) with pre-programmed behavior. They support the full test spy API in addition to methods which can be used to alter the stub’s behavior. 28 | This is a key point here as well with stubs you get the full spy api but with Mocks you don't. 29 | 30 | #### Sinon.js Spies via explanation [Sinon Spies](http://sinonjs.org/docs/#spies) 31 | A test spy is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls. 32 | Test spies are useful to test both callbacks and how certain functions/methods are used throughout the system under test. 33 | 34 | *Sinon has an assertion api that you can reference here [Sinon Assertions](http://sinonjs.org/docs/#assertions)* 35 | *You can use either Mocha or Tape.js here it is your choice which one you feel most comfortable with.* 36 | 37 | #### Sinon has an assertion library that you can use but I would recommend using either chai.js assertion library or should.js 38 | * Chai.js documentation can be found here [Chai.js Assert](http://chaijs.com/api/assert/) // [Chai.js BDD](http://chaijs.com/api/bdd/) 39 | * Should.js Assertion library can be found here [Should.js](http://shouldjs.github.io/) 40 | 41 | **Open program.test.js in `mocks-stubs-spies` folder** 42 | 43 | #### 1. Stub the `retrieveDocument` function 44 | ```javascript 45 | function retrieveDocument({dbName, name}) { 46 | const couchDBName = nano.use(dbName); 47 | return new Promise((resolve, reject) => { 48 | couchDBName.get(name, (err, body) => { 49 | if (!err) { 50 | resolve(body); 51 | } 52 | reject(err); 53 | }); 54 | }); 55 | } 56 | ``` 57 | 58 | You don't need the implementation here but I included it here for your reference. 59 | Using Sinon check that the retrieveDocument stub is called once. 60 | Make an assertion that the payload and the expected response match. 61 | 62 | #### 2. Stub the `insertDocument` function. 63 | 64 | ```javascript 65 | function insertDocument({ dbName = 'softwaretesting', name = 'users', body } = {}) { 66 | return new Promise((resolve, reject) => { 67 | const couchDBName = nano.use(dbName); 68 | return insertDoc({ dbName: couchDBName, name, body }) 69 | .then(() => { 70 | resolve(retrieveDoc({ dbName: couchDBName , name })); 71 | }) 72 | .catch(err => { 73 | reject(err); 74 | }); 75 | }); 76 | } 77 | 78 | function insertDoc({dbName, name, body}) { 79 | return new Promise((resolve, reject) => { 80 | dbName.insert(body, name, (err, body, header) => { 81 | if (!err) { 82 | resolve(body); 83 | } else { 84 | reject(err); 85 | } 86 | }); 87 | }); 88 | } 89 | ``` 90 | 91 | *Again you don't need the implementation here because you are stubbing it out but I added it in case you are curious.* 92 | 93 | * Use sinon to make some assertions about the stubbed out function 94 | * Remember to use the setup function in tape or the before block in mocha to initialize the stub. 95 | 96 | #### 3. Stub out the `deleteDocument` function 97 | 98 | ###### Implementation Details 99 | ```javascript 100 | function retrieveDoc({dbName, name}) { 101 | return new Promise((resolve, reject) => { 102 | dbName.get(name, (err, body) => { 103 | if (!err) { 104 | resolve(body); 105 | } 106 | reject(err); 107 | }); 108 | }); 109 | } 110 | 111 | function deleteDocument({dbName, name}) { 112 | const couchDBName = nano.use(dbName); 113 | return retrieveDoc({dbName: couchDBName, name}) 114 | .then(body => { 115 | if (body) { 116 | const { 117 | _rev 118 | } = body; 119 | couchDBName.destroy(name, _rev, (err, body) => { 120 | if (!err) { 121 | return body; 122 | } 123 | throw err; 124 | }); 125 | } 126 | }); 127 | } 128 | ``` 129 | 130 | * Stub out the deleteDocument function by using sinon. 131 | * Use chai assertions or use the should.js assertion library 132 | -------------------------------------------------------------------------------- /docs/property/property.md: -------------------------------------------------------------------------------- 1 | ## Property Based Testing 2 | 3 | `Dijkstra's` 4 | > Program testing can at best show the presence of errors, but never their absence 5 | 6 | Author states that 7 | > Thus we can expect testing to be the main form of program verification fora long time to come—it is the only practical technique in most cases 8 | 9 | The point is made that with a CI process in place you can automate testing in your code base but there is still a dilemma on how many test cases to write. 10 | 11 | Do you write one test case or many test cases? 12 | 13 | > In practice, much time is devoted either to simplifying a failing case by hand, or to debugging and tracing a complex case to understand why it fails. Shrinking failing cases automates the first stage of diagnosis, and makes the step from automated testing to locating a fault very short indeed 14 | 15 | #### Test Case Wisdom 16 | * During regular test case scenarios in unit-testing your follow the happy path or normal path 17 | * This in turn forms basis for future test cases 18 | * By generating test cases you can find bugs faster and more accuracy is what I am gleaning from the paper 19 | 20 | * It is better to run smaller tests than large tests. 21 | * Most errors can be found by a smaller test case. 22 | 23 | * Developer will jump onto the first failing case 24 | * Rerun the test case and start debugging the issue 25 | * Test cases generated by hand are time consuming as well 26 | * When new test cases can be generated by hand in seconds it helps reduce developer time on trivial edge cases. 27 | 28 | ### TestCheck.js 29 | 30 | Generative property testing for JavaScript. 31 | 32 | TestCheck.js is a library for generative testing of program properties, ala QuickCheck. 33 | 34 | * By providing a specification of the JavaScript program in the form of properties 35 | * Properties can be tested to remain true for a large number of randomly generated cases. 36 | * In the case of a test failure, the smallest possible failing test case is found. 37 | 38 | 39 | ```javascript 40 | const { check, gen, property } = require('testcheck'); 41 | const test = require('tape'); 42 | 43 | test('addition is commutative', check(gen.int, gen.int, (t, numA, numB) => { 44 | t.plan(1); 45 | t.equal(numA + numB, numB + numA) 46 | })); 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/tdd/tdd.md: -------------------------------------------------------------------------------- 1 | ## Test-Driven Development (BDD) 2 | 3 | To view lecture notes for this course, please consult the [github-pages](https://jbelmont.github.io/software-testing). 4 | 5 | #### Definition of TDD via Wikipedia [Test-Driven Development](https://en.wikipedia.org/wiki/Test-driven_development) 6 | Each test case fails initially: This ensures that the test really works and can catch an error. Once this is shown, the underlying functionality can be implemented. This has led to the "test-driven development mantra", which is "red/green/refactor", where red means fail and green means pass. Test-driven development constantly repeats the steps of adding test cases that fail, passing them, and refactoring. Receiving the expected test results at each stage reinforces the developer's mental model of the code, boosts confidence and increases productivity. 7 | 8 | #### Test-Driven Development Cycle 9 | **_1. Add a test_** 10 | **_2. Run all tests and see if the new test fails_** 11 | **_3. Write the code_** 12 | **_4. Run tests_** 13 | **_5. Refactor code._** 14 | **_Repeat_** 15 | 16 | #### Exercise Instructions 17 | Requirements for First TDD Cycle 18 | Create a function that computes the average of a range of numbers. 19 | Please go to slide xyz in [github-pages](https://jbelmont.github.io/software-testing) 20 | 21 | ##### Test-Driven Development Cycle 1 (Add a Test / Run Tests) 22 | 1. Go to file path `tdd/tdd-cycle/cycle1/program.test.js` and add a failing test by calling a function that doesn't exist in program.js 23 | 2. Add a failing test in program.test.js using either Mocha with Chai or with Tape from our previous exercies. 24 | 3. Run the failing test `npm run tdd:cycle1` 25 | 26 | ##### Test-Driven Development Cycle 2 (Write the Code / Run Tests) 27 | 1. Go to file path `tdd/tdd-cycle/cycle2`. 28 | 2. Add the minimal requirement to make the test pass again. 29 | 3. (Hint) Add an empty function in `program.js` and then call it with the appropriate assertion. 30 | 4. Run the test with `npm run tdd:cycle2` 31 | 32 | ##### Test-Driven Development Cycle 3 (Refactor by adding implementation / Add a test / Run all Tests again) 33 | 1. Go to file path `tdd/tdd-cycle/cycle3`. 34 | 2. Implement the average function in program.js. 35 | 3. Add a unit test for the average function with an array of numbers. 36 | 4. Use appropriate assertion to unit test the function. 37 | 5. Run the test with `npm run tdd:cycle3` 38 | 39 | ##### Test-Driven Development Cycle Final / (Refactor code / Add a test / Run all tests again) 40 | 1. Go to the file path `tdd/tdd-cycle/cyclefinal`. 41 | 2. Refactor the code again with possible different implementation or quit. 42 | 3. If refactored with newer function than add new test else add run the same test for original implementation 43 | 4. Run the test with `npm run tdd:cycle:final` 44 | -------------------------------------------------------------------------------- /docs/unit/unit.md: -------------------------------------------------------------------------------- 1 | ## Unit Tests 2 | 3 | To view lecture notes for this course, please consult the 4 | [github-pages](https://jbelmont.github.io/software-testing). 5 | 6 | [Rediscovery of TDD](https://www.quora.com/Why-does-Kent-Beck-refer-to-the-rediscovery-of-test-driven-development) 7 | 8 | Instructions for Unit Exercises: 9 | 1. Go to unit folder `cd unit` 10 | 2. Open program.test.js and go to each TODO block. 11 | 3. Complete each todo block by adding unit tests. 12 | 4. Please run the following script to `npm run unit:test` in order to do the unit test exercises 13 | 14 | ### 1. Unit Test the Map Function: 15 | ```js 16 | nest.test('Unit test the map function', assert => { 17 | assert.equal(actual, expected, 18 | `should render default message`); 19 | assert.end(); 20 | }); 21 | ``` 22 | 23 | #### For a typical unit test I usually create 2 variables one named actual and another named expect 24 | *For `assert.equal(actual, expected, 'My message here')` if actual and expected are equal then the unit test will pass.* 25 | 26 | The map function behaves in the following manner 27 | ```js 28 | [1,2,3,4,5].map(function(number) { 29 | return { 30 | value: number 31 | }; 32 | }); 33 | ``` 34 | 35 | This will return the following structure 36 | ```json 37 | [ 38 | { value: 1 }, 39 | { value: 2 }, 40 | ... 41 | ] 42 | ``` 43 | 44 | Add variables actual and expected to this first unit test. 45 | The equal method expects to get single property/value in order to pass 1 === 1 or "Mike" === "Mike" 46 | The deepEqual method does a deep property check like this [1,2,3] === [1,2,3] 47 | 48 | ### 2. Unit Test the Filter Function. 49 | 50 | The filter function behaves in the following manner 51 | ```js 52 | [1,2,3,4,5].filter(function(number) { 53 | return number > 3; 54 | }); 55 | ``` 56 | 57 | This will return the following structure 58 | `[4, 5]` 59 | 60 | *Either choose `assert.equal` or `assert.deepEqual` but remember deepEqual does a deep check with arrays but equal checks properties.* 61 | 62 | ### 3. Unit Test the concatAll Function. 63 | 64 | The concatAll function behaves in the following manner 65 | ```js 66 | [ 67 | [1,2,3,4,5], 68 | [6,7,8,9,10] 69 | ].concatAll(); 70 | ``` 71 | 72 | This will return the following structure 73 | `[1,2,3,4,5,6,7,8,9,10]` 74 | 75 | ##### Write a Unit Test using the same format as previous 2 exercises. 76 | ```js 77 | nest.test('I am some text', assert => { 78 | const actual = ...; 79 | const expected = ...; 80 | assert.equal( 81 | actual, 82 | expected, 83 | 'I should another text' 84 | ); 85 | assert.end(); 86 | }); 87 | ``` 88 | 89 | ### 4. Unit Test the concatMap Function. 90 | 91 | The concatMap function behaves in the following manner 92 | ```js 93 | const numStrings = [ ["One", "Two", "Three"], ["Four", "Five", "Six"] ]; 94 | [1, 2, 3, 4, 5].concatMap(function(num) { 95 | return numStrings[num]; 96 | }); 97 | ``` 98 | 99 | This will return the following structure 100 | `["One", "Two", "Three", "Four", "Five", "Six"]` 101 | -------------------------------------------------------------------------------- /end-to-end-tests/instructions.md: -------------------------------------------------------------------------------- 1 | ## End-to-End Tests 2 | 3 | To view lecture notes for this course, please consult the [github-pages](https://jbelmont.github.io/software-testing). 4 | 5 | #### Definition of End-to-End Tests via InfoQ [End-To-End-Testing](https://www.infoq.com/articles/balancing-unit-and-end-to-end-tests) 6 | End-to-End Tests simulate user behavior. In a web application, they will start the server, fire up a browser, click around, and assert that certain things happening in the browser give us confidence our feature is working. These tests give great confidence, but they are slow, brittle, and tightly coupled to the user interface. 7 | 8 | * End-to-end testing is used to test whether the flow of an application from start to finish is behaving as expected. 9 | * The purpose of performing end-to-end testing is to identify system dependencies 10 | * Ensure that the data integrity is maintained between various system components and systems. 11 | 12 | ## Why write end to end tests? 13 | 14 | * Modern software systems are complex and are interconnected with multiple sub-systems 15 | * A sub-system may be different from the current system or may be owned by another organization. 16 | * If any one of the sub-system fails, the whole software system could collapse. 17 | * This is major risk and can be avoided by End-to-End testing. End-to-End testing verifies the complete system flow. 18 | * It increase test coverage of various sub-systems. 19 | * It helps detect issues with sub-systems and increases confidence in the overall software product. 20 | 21 | ## Nightwatch.js 22 | 23 | We will be using the nightwatch.js end to end testing library 24 | 25 | 26 | -------------------------------------------------------------------------------- /end-to-end-tests/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | require('../config/config'); 3 | 4 | // http://nightwatchjs.org/guide#settings-file 5 | module.exports = { 6 | "src_folders" : ["end-to-end-tests/nightwatch"], 7 | "output_folder" : "reports", 8 | "custom_commands_path" : "", 9 | "custom_assertions_path" : "", 10 | "page_objects_path" : ["end-to-end-tests/page-objects"], 11 | "globals_path" : "", 12 | 13 | "selenium" : { 14 | "start_process" : true, 15 | "host": "127.0.0.1", 16 | "server_path" : "node_modules/selenium-server/lib/runner/selenium-server-standalone-3.4.0.jar", 17 | "log_path" : "", 18 | "port" : 4444, 19 | "cli_args" : { 20 | "webdriver.chrome.driver" : require('chromedriver').path 21 | } 22 | }, 23 | 24 | "test_settings" : { 25 | "default" : { 26 | "selenium_port": 4444, 27 | "selenium_host": "localhost", 28 | "silent": true, 29 | "screenshots" : { 30 | "enabled" : false, 31 | "path" : "" 32 | }, 33 | "launch_url": "https://localhost:3000", 34 | "globals": { 35 | "devServerURL": "https://localhost:" + (process.env.PORT) 36 | } 37 | }, 38 | 39 | "chrome" : { 40 | "desiredCapabilities": { 41 | "browserName": "chrome", 42 | "javascriptEnabled" : true, 43 | "acceptSslCerts" : true, 44 | "chromeOptions" : { 45 | "args" : ["start-fullscreen"] 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /end-to-end-tests/nightwatch.runner.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'testing'; 2 | process.env.PORT = 3000; 3 | const server = require('../app').listen(process.env.PORT); 4 | const spawn = require('cross-spawn'); // eslint-disable-line 5 | 6 | // 2. run the nightwatch test suite against it 7 | // to run in additional browsers: 8 | // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings" 9 | // 2. add it to the --env flag below 10 | // or override the environment flag, for example: `npm run end:to:end:test -- --env chrome,firefox` 11 | // For more information on Nightwatch's config file, see 12 | // http://nightwatchjs.org/guide#settings-file 13 | let opts = process.argv.slice(2); 14 | 15 | if (opts.indexOf('--config') === -1) { 16 | opts = opts.concat(['--config', 'end-to-end-tests/nightwatch.conf.js']); 17 | } 18 | if (opts.indexOf('--env') === -1) { 19 | opts = opts.concat(['--env', 'chrome']); 20 | } 21 | const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }); 22 | 23 | runner.on('exit', (code) => { 24 | server.close(); 25 | process.exit(code); 26 | }); 27 | 28 | runner.on('error', (err) => { 29 | server.close(); 30 | throw err; 31 | }); 32 | -------------------------------------------------------------------------------- /end-to-end-tests/nightwatch/codeCraftsmanshipSaturdays.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | 'Code Craftsmanship Saturdays': browser => { 5 | // TODO: Go to url http://localhost:3000 and make assertion 6 | 7 | // TODO: Click trash can and make assertion 8 | 9 | // TODO: Add a new user and assert they are added 10 | 11 | // TODO: Go to an individual user and make assertion 12 | 13 | // Remove Me Please 14 | browser.end(); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /end-to-end-tests/page-objects/codeCraftsmanshipSaturdaysPage.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | url: 'https://localhost:3000', 3 | elements: { 4 | addUserButton: { 5 | selector: '.add-user-btn-container > button' 6 | }, 7 | addSomeUserBtn: { 8 | selector: '#addSomeUserBtn' 9 | }, 10 | codeCraftsmanshipContainerLabel: { 11 | selector: '.code-craftsmanship-container-label' 12 | }, 13 | codeCraftsmanshipContainerStrongLabel: { 14 | selector: '.code-craftsmanship-container-label > strong' 15 | }, 16 | trashBin: { 17 | selector: '.users-container .users-container-trash-bin' 18 | }, 19 | emailInput: { 20 | selector: '#emailInput' 21 | }, 22 | firstNameInput: { 23 | selector: '#firstNameInput' 24 | }, 25 | lastNameInput: { 26 | selector: '#lastNameInput' 27 | }, 28 | addUserSubmit: { 29 | selector: '#addUserSubmit' 30 | } 31 | }, 32 | commands: [ 33 | { 34 | setInput: function (input, value) { 35 | this.setValue(input, value); 36 | } 37 | } 38 | ] 39 | }; 40 | -------------------------------------------------------------------------------- /end-to-end-tests/solution/nightwatch/nightwatch.solution.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | 'Code Craftsmanship Saturdays': browser => { 5 | browser 6 | .url('http://localhost:3000') 7 | .waitForElementVisible('.code-craftsmanship-container-label', 1000) 8 | .assert.containsText('.code-craftsmanship-container-label > strong', 'Code Craftsmanship Saturdays') 9 | 10 | browser 11 | .click('.users-container .users-container-trash-bin') 12 | .assert.elementNotPresent('[data-email="tcox0@dion.ne.jp"]') 13 | 14 | browser 15 | .click('#addSomeUserBtn') 16 | 17 | setInput('#emailInput', 'chuck@badass.net') 18 | setInput('#firstNameInput', 'Chuck') 19 | setInput('#lastNameInput', 'Norris') 20 | 21 | browser 22 | .click('#addUserSubmit') 23 | .waitForElementVisible('[data-email="chuck@badass.net"]', 1000) 24 | 25 | browser 26 | .click('[data-email="dpayne3@cdbaby.com"]') 27 | .assert.urlContains('user') 28 | .end(); 29 | 30 | function setInput(input, value) { 31 | browser.setValue(input, value); 32 | } 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /end-to-end-tests/solution/nightwatch/nightwatch.solution.page.objects.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | 'Code Craftsmanship Saturdays': browser => { 5 | const codeSaturdays = browser.page.codeCraftsmanshipSaturdaysPage(); 6 | 7 | codeSaturdays 8 | .navigate() 9 | .waitForElementVisible('@codeCraftsmanshipContainerLabel', 1000) 10 | .assert.containsText('@codeCraftsmanshipContainerStrongLabel', 'Code Craftsmanship Saturdays') 11 | 12 | codeSaturdays 13 | .click('@trashBin') 14 | .assert.elementNotPresent('[data-email="tcox0@dion.ne.jp"]') 15 | 16 | codeSaturdays 17 | .click('@addSomeUserBtn') 18 | 19 | codeSaturdays.setInput('#emailInput', 'chuck@badass.net') 20 | codeSaturdays.setInput('#firstNameInput', 'Chuck') 21 | codeSaturdays.setInput('#lastNameInput', 'Norris') 22 | 23 | codeSaturdays 24 | .click('@addUserSubmit') 25 | .waitForElementVisible('[data-email="chuck@badass.net"]', 1000) 26 | 27 | codeSaturdays 28 | .click('[data-email="dpayne3@cdbaby.com"]') 29 | .assert.urlContains('user') 30 | .end(); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const webpack = require('webpack'); 3 | const sourcemaps = require('gulp-sourcemaps'); 4 | const sass = require('gulp-sass'); 5 | const autoprefixer = require('gulp-autoprefixer'); 6 | const uglify = require('gulp-uglify'); 7 | const concat = require('gulp-concat'); 8 | const runSequence = require('run-sequence'); 9 | const gutil = require('gulp-util'); 10 | const merge = require('merge-stream'); 11 | const nodemon = require('gulp-nodemon'); 12 | const livereload = require('gulp-livereload'); 13 | 14 | // Load Environment constiables 15 | require('dotenv').config(); 16 | const webpackConfig = process.env.NODE_ENV === 'development' 17 | ? require('./webpack.config.js') 18 | : require('./webpack.config.prod.js'); 19 | 20 | const jsPaths = [ 21 | 'static/js/components/*.js' 22 | ]; 23 | const sassPaths = [ 24 | 'static/scss/*.scss', 25 | './node_modules/bootstrap/dist/css/bootstrap.min.css' 26 | ]; 27 | 28 | const filesToCopy = [ 29 | { 30 | src: './node_modules/react/dist/react.min.js', 31 | dest: './static/build' 32 | }, 33 | { 34 | src: './node_modules/react-dom/dist/react-dom.min.js', 35 | dest: './static/build' 36 | }, 37 | { 38 | src: './node_modules/react-bootstrap/dist/react-bootstrap.min.js', 39 | dest: './static/build' 40 | }, 41 | { 42 | src: './images/favicon.ico', 43 | dest: './static/build' 44 | }, 45 | 46 | { 47 | src: './icomoon/symbol-defs.svg', 48 | dest: './static/build' 49 | } 50 | ]; 51 | 52 | gulp.task('copy:react:files', () => { 53 | const streams = []; 54 | filesToCopy.forEach(file => { 55 | streams.push(gulp.src(file.src).pipe(gulp.dest(file.dest))); 56 | }); 57 | return merge.apply(this, streams); 58 | }); 59 | 60 | gulp.task('uglify:js', () => { 61 | return gulp.src(jsPaths) 62 | .pipe(uglify()) 63 | .pipe(gulp.dest('static/build')); 64 | }); 65 | 66 | gulp.task('build:js', (callback) => { 67 | webpack(Object.create(webpackConfig), (err, stats) => { 68 | if (err) { 69 | throw new gutil.PluginError('build:js', err); 70 | } 71 | gutil.log('[build:js]', stats.toString({colors: true, chunks: false})); 72 | callback(); 73 | }); 74 | }); 75 | 76 | gulp.task('build:sass', () => { 77 | return gulp.src(sassPaths[0]) 78 | .pipe(sourcemaps.init()) 79 | .pipe(sass({ 80 | outputStyle: 'compressed', 81 | includePaths: ['node_modules'] 82 | })) 83 | .pipe(autoprefixer({cascade: false})) 84 | .pipe(concat('software-testing.css')) 85 | .pipe(sourcemaps.write('.')) 86 | .pipe(gulp.dest('./static/build')) 87 | .pipe(livereload()); 88 | }); 89 | 90 | gulp.task('build:vendor:sass', () => { 91 | return gulp.src([...sassPaths.slice(1)]) 92 | .pipe(sourcemaps.init()) 93 | .pipe(sass({ 94 | outputStyle: 'compressed', 95 | includePaths: ['node_modules'] 96 | })) 97 | .pipe(autoprefixer({cascade: false})) 98 | .pipe(concat('vendor.css')) 99 | .pipe(sourcemaps.write('.')) 100 | .pipe(gulp.dest('./static/build')); 101 | }); 102 | 103 | gulp.task('watch:js', () => { 104 | let config = Object.create(webpackConfig); 105 | config.watch = true; 106 | webpack(config, (err, stats) => { 107 | if (err) { 108 | throw new gutil.PluginError('watch:js', err); 109 | } 110 | gutil.log('[watch:js]', stats.toString({colors: true, chunks: false})); 111 | }); 112 | gulp.watch('static/js/components/*.js', ['uglify:js', 'build:js']); 113 | }); 114 | 115 | gulp.task('watch:sass', () => { 116 | gulp.watch('static/scss/*.scss', ['build:sass']); 117 | }); 118 | 119 | gulp.task('start', () => { 120 | nodemon({ 121 | script: './bin/www', 122 | ignore: ['static/*'], 123 | env: { 'PORT': '3000' } 124 | }); 125 | }); 126 | 127 | gulp.task('debug', () => { 128 | nodemon({ 129 | exec: 'node-inspector & node --inspect', 130 | ext: 'js', 131 | ignore: ['static/*'], 132 | script: './bin/www', 133 | verbose: true 134 | }); 135 | }); 136 | 137 | gulp.task('build', (cb) => { 138 | runSequence('copy:react:files', 'uglify:js', 'build:js', 'build:sass', 'build:vendor:sass', cb); 139 | }); 140 | 141 | gulp.task('dev', (cb) => { 142 | livereload.listen(); 143 | runSequence('copy:react:files', 'uglify:js', 'build:sass', 'build:vendor:sass', ['watch:js', 'watch:sass'], 'start', cb); 144 | }); 145 | 146 | gulp.task('dev:debug', (cb) => { 147 | livereload.listen(); 148 | runSequence('copy:react:files', 'uglify:js', 'build:sass', 'build:vendor:sass', ['watch:js', 'watch:sass'], 'debug', cb); 149 | }); 150 | -------------------------------------------------------------------------------- /icomoon/PNG/arrow-down-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/arrow-down-left.png -------------------------------------------------------------------------------- /icomoon/PNG/arrow-down-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/arrow-down-right.png -------------------------------------------------------------------------------- /icomoon/PNG/arrow-down6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/arrow-down6.png -------------------------------------------------------------------------------- /icomoon/PNG/arrow-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/arrow-left.png -------------------------------------------------------------------------------- /icomoon/PNG/arrow-left2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/arrow-left2.png -------------------------------------------------------------------------------- /icomoon/PNG/arrow-left3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/arrow-left3.png -------------------------------------------------------------------------------- /icomoon/PNG/arrow-left4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/arrow-left4.png -------------------------------------------------------------------------------- /icomoon/PNG/arrow-left5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/arrow-left5.png -------------------------------------------------------------------------------- /icomoon/PNG/arrow-left6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/arrow-left6.png -------------------------------------------------------------------------------- /icomoon/PNG/arrow-right3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/arrow-right3.png -------------------------------------------------------------------------------- /icomoon/PNG/arrow-right5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/arrow-right5.png -------------------------------------------------------------------------------- /icomoon/PNG/arrow-right6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/arrow-right6.png -------------------------------------------------------------------------------- /icomoon/PNG/arrow-up-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/arrow-up-left.png -------------------------------------------------------------------------------- /icomoon/PNG/arrow-up-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/arrow-up-right.png -------------------------------------------------------------------------------- /icomoon/PNG/arrow-up2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/arrow-up2.png -------------------------------------------------------------------------------- /icomoon/PNG/arrow-up3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/arrow-up3.png -------------------------------------------------------------------------------- /icomoon/PNG/arrow-up4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/arrow-up4.png -------------------------------------------------------------------------------- /icomoon/PNG/arrow-up5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/arrow-up5.png -------------------------------------------------------------------------------- /icomoon/PNG/arrow-up6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/arrow-up6.png -------------------------------------------------------------------------------- /icomoon/PNG/bin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/bin.png -------------------------------------------------------------------------------- /icomoon/PNG/bin2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/bin2.png -------------------------------------------------------------------------------- /icomoon/PNG/box-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/box-add.png -------------------------------------------------------------------------------- /icomoon/PNG/bubble-dots2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/bubble-dots2.png -------------------------------------------------------------------------------- /icomoon/PNG/bubble7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/bubble7.png -------------------------------------------------------------------------------- /icomoon/PNG/cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/cart.png -------------------------------------------------------------------------------- /icomoon/PNG/cart4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/cart4.png -------------------------------------------------------------------------------- /icomoon/PNG/checkbox-checked2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/checkbox-checked2.png -------------------------------------------------------------------------------- /icomoon/PNG/checkmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/checkmark.png -------------------------------------------------------------------------------- /icomoon/PNG/circle-down2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/circle-down2.png -------------------------------------------------------------------------------- /icomoon/PNG/circle-up2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/circle-up2.png -------------------------------------------------------------------------------- /icomoon/PNG/cog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/cog.png -------------------------------------------------------------------------------- /icomoon/PNG/cog7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/cog7.png -------------------------------------------------------------------------------- /icomoon/PNG/credit-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/credit-card.png -------------------------------------------------------------------------------- /icomoon/PNG/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/cross.png -------------------------------------------------------------------------------- /icomoon/PNG/cross2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/cross2.png -------------------------------------------------------------------------------- /icomoon/PNG/cross3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/cross3.png -------------------------------------------------------------------------------- /icomoon/PNG/enlarge2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/enlarge2.png -------------------------------------------------------------------------------- /icomoon/PNG/enlarge7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/enlarge7.png -------------------------------------------------------------------------------- /icomoon/PNG/facebook2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/facebook2.png -------------------------------------------------------------------------------- /icomoon/PNG/file-check2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/file-check2.png -------------------------------------------------------------------------------- /icomoon/PNG/folder-plus4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/folder-plus4.png -------------------------------------------------------------------------------- /icomoon/PNG/folder-remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/folder-remove.png -------------------------------------------------------------------------------- /icomoon/PNG/folder3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/folder3.png -------------------------------------------------------------------------------- /icomoon/PNG/google-plus2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/google-plus2.png -------------------------------------------------------------------------------- /icomoon/PNG/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/home.png -------------------------------------------------------------------------------- /icomoon/PNG/home4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/home4.png -------------------------------------------------------------------------------- /icomoon/PNG/lock2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/lock2.png -------------------------------------------------------------------------------- /icomoon/PNG/pencil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/pencil.png -------------------------------------------------------------------------------- /icomoon/PNG/pencil2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/pencil2.png -------------------------------------------------------------------------------- /icomoon/PNG/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/play.png -------------------------------------------------------------------------------- /icomoon/PNG/play2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/play2.png -------------------------------------------------------------------------------- /icomoon/PNG/plus3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/plus3.png -------------------------------------------------------------------------------- /icomoon/PNG/rss2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/rss2.png -------------------------------------------------------------------------------- /icomoon/PNG/shrink7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/shrink7.png -------------------------------------------------------------------------------- /icomoon/PNG/skype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/skype.png -------------------------------------------------------------------------------- /icomoon/PNG/spinner10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/spinner10.png -------------------------------------------------------------------------------- /icomoon/PNG/spinner3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/spinner3.png -------------------------------------------------------------------------------- /icomoon/PNG/spinner7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/spinner7.png -------------------------------------------------------------------------------- /icomoon/PNG/spinner9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/spinner9.png -------------------------------------------------------------------------------- /icomoon/PNG/stack3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/stack3.png -------------------------------------------------------------------------------- /icomoon/PNG/tumblr2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/tumblr2.png -------------------------------------------------------------------------------- /icomoon/PNG/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/twitter.png -------------------------------------------------------------------------------- /icomoon/PNG/unlocked2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/unlocked2.png -------------------------------------------------------------------------------- /icomoon/PNG/user3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/user3.png -------------------------------------------------------------------------------- /icomoon/PNG/user4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/user4.png -------------------------------------------------------------------------------- /icomoon/PNG/user5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/user5.png -------------------------------------------------------------------------------- /icomoon/PNG/user6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/icomoon/PNG/user6.png -------------------------------------------------------------------------------- /icomoon/Read Me.txt: -------------------------------------------------------------------------------- 1 | The *SVG* folder contains the icons you selected as separate SVG files. 2 | 3 | If you prefer using PNGs, PDFs, or CSS sprites, refer to the Preferences panel of the IcoMoon app before downloading your zip pack. 4 | 5 | *demo.html* lists the icons that you selected. To insert your icons as inline SVGs (with the element), copy the element (that contains symbol definitions) from the source of the demo.html file, below your own HTML's tag. After copying this SVG, you can reference your glyphs like the following: 6 | 7 | 8 | 9 | You can get this code from the SVG tab of the IcoMoon app, or by referring to the source of the demo.html file. To see how you can change the color/size of your icons using CSS, refer to the example provided in the *style.css* file. 10 | 11 | If you prefer to reference an external SVG (containing ) instead of embedding it in HTML, you will need to use *svgxuse.js* in order to support IE 9+. In browsers that don't support referencing external SVGs (such as IE 9), this polyfill sends one HTTP request to fetch and cache all symbol definitions. See *demo-external-svg.html* for this approach. This demo references the *symbol-defs.svg* file and uses the aforementioned polyfill. Note that it must be hosted on a web server to work 12 | properly. Learn more aobut this polyfill here: https://github.com/Keyamoon/svgxuse 13 | 14 | You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu → Manage Projects) to retrieve your icon selection. 15 | 16 | -------------------------------------------------------------------------------- /icomoon/SVG/arrow-down-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/arrow-down-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/arrow-down6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/arrow-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/arrow-left2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/arrow-left3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/arrow-left4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/arrow-left5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/arrow-left6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/arrow-right3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/arrow-right5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/arrow-right6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/arrow-up-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/arrow-up-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/arrow-up2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/arrow-up3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/arrow-up4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/arrow-up5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/arrow-up6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/bin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /icomoon/SVG/bin2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/box-add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/bubble-dots2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/bubble7.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/cart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /icomoon/SVG/cart4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/checkbox-checked2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/checkmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/circle-down2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /icomoon/SVG/circle-up2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /icomoon/SVG/cog.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/cog7.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /icomoon/SVG/credit-card.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/cross.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/cross2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/cross3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/enlarge2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /icomoon/SVG/enlarge7.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/facebook2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/file-check2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /icomoon/SVG/folder-plus4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/folder-remove.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/folder3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/google-plus2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/home4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/lock2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/pencil.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/pencil2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/play2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/plus3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/rss2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/shrink7.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/skype.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /icomoon/SVG/spinner3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /icomoon/SVG/spinner7.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/spinner9.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/stack3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/tumblr2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/unlocked2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/user3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/user4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/user5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icomoon/SVG/user6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /icomoon/demo-files/demo.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 0; 3 | margin: 0; 4 | font-family: sans-serif; 5 | font-size: 1em; 6 | line-height: 1.5; 7 | color: #555; 8 | background: #fff; 9 | } 10 | h1 { 11 | font-size: 1.5em; 12 | font-weight: normal; 13 | box-shadow: 0 1px #ddd, 0 2px #fff, 0 3px #ddd; 14 | } 15 | small { 16 | font-size: .66666667em; 17 | } 18 | a { 19 | color: #e74c3c; 20 | text-decoration: none; 21 | } 22 | a:hover, a:focus { 23 | box-shadow: 0 1px #e74c3c; 24 | } 25 | .bshadow0, input { 26 | box-shadow: inset 0 -2px #e7e7e7; 27 | } 28 | input:hover { 29 | box-shadow: inset 0 -2px #ccc; 30 | } 31 | input, fieldset { 32 | font-size: 1em; 33 | margin: 0; 34 | padding: 0; 35 | border: 0; 36 | } 37 | input { 38 | color: inherit; 39 | line-height: 1.5; 40 | height: 1.5em; 41 | padding: .25em 0; 42 | } 43 | input:focus { 44 | outline: none; 45 | box-shadow: inset 0 -2px #449fdb; 46 | } 47 | .glyph { 48 | font-size: 16px; 49 | width: 17em; 50 | margin-right: 1.5em; 51 | float: left; 52 | overflow: hidden; 53 | } 54 | svg { 55 | color: #000; 56 | } 57 | .liga { 58 | width: 80%; 59 | width: calc(100% - 2.5em); 60 | } 61 | .talign-right { 62 | text-align: right; 63 | } 64 | .talign-center { 65 | text-align: center; 66 | } 67 | .bgc1 { 68 | background: #f1f1f1; 69 | } 70 | .fgc0 { 71 | color: #000; 72 | } 73 | .fgc1 { 74 | color: #999; 75 | } 76 | p { 77 | margin-top: 1em; 78 | margin-bottom: 1em; 79 | } 80 | .mvm { 81 | margin-top: .75em; 82 | margin-bottom: .75em; 83 | } 84 | .mtn { 85 | margin-top: 0; 86 | } 87 | .mtl, .mal { 88 | margin-top: 1.5em; 89 | } 90 | .mbl, .mal { 91 | margin-bottom: 1.5em; 92 | } 93 | .mal, .mhl { 94 | margin-left: 1.5em; 95 | margin-right: 1.5em; 96 | } 97 | .mhmm { 98 | margin-left: 1em; 99 | margin-right: 1em; 100 | } 101 | .mls { 102 | margin-left: .25em; 103 | } 104 | .ptl { 105 | padding-top: 1.5em; 106 | } 107 | .pbs, .pvs { 108 | padding-bottom: .25em; 109 | } 110 | .pvs, .pts { 111 | padding-top: .25em; 112 | } 113 | .unit { 114 | float: left; 115 | } 116 | .unitRight { 117 | float: right; 118 | } 119 | .size1of2 { 120 | width: 50%; 121 | } 122 | .size1of1 { 123 | width: 100%; 124 | } 125 | .clearfix:before, .clearfix:after { 126 | content: " "; 127 | display: table; 128 | } 129 | .clearfix:after { 130 | clear: both; 131 | } 132 | .hidden-true { 133 | display: none; 134 | } 135 | .textbox0 { 136 | width: 3em; 137 | background: #f1f1f1; 138 | padding: .25em .5em; 139 | line-height: 1.5; 140 | height: 1.5em; 141 | } 142 | .fs0 { 143 | font-size: 16px; 144 | } 145 | .fs1 { 146 | font-size: 16px; 147 | } 148 | 149 | -------------------------------------------------------------------------------- /icomoon/style.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | display: inline-block; 3 | width: 1em; 4 | height: 1em; 5 | stroke-width: 0; 6 | stroke: currentColor; 7 | fill: currentColor; 8 | } 9 | 10 | /* ========================================== 11 | Single-colored icons can be modified like so: 12 | .icon-name { 13 | font-size: 32px; 14 | color: red; 15 | } 16 | ========================================== */ 17 | -------------------------------------------------------------------------------- /images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/images/favicon.ico -------------------------------------------------------------------------------- /integration-tests/instructions.md: -------------------------------------------------------------------------------- 1 | ## Integration Tests 2 | 3 | To view lecture notes for this course, please consult the [github-pages](https://jbelmont.github.io/software-testing). 4 | 5 | ## Distinction between Unit Tests 6 | *Introducing dependencies on external modules or data also turns unit tests into integration tests.* 7 | 8 | *If one module misbehaves in a chain of interrelated modules, it is not so immediately clear where to look for the cause of the failure.* 9 | *When code under development relies on a database, a web service, or any other external process or service,* 10 | *enforcing a unit-testable separation is also an opportunity and a driving force to design more modular, more testable and more reusable code.* 11 | 12 | [Distinction between Unit Tests](https://en.wikipedia.org/wiki/Test-driven_development) 13 | 14 | ## Starting instructions 15 | * Open 2 terminal prompts 16 | * run the command `couchdb` 17 | * `cd integration-tests` 18 | ### II. Using text editor of your choice open program.test.js and complete each TODO block. 19 | 20 | ### 1. Finish Integration test for the /api/v1/users/badMofos endpoint: 21 | 22 | #### Inspect the payload with the following curl command 23 | ```sh 24 | curl -X GET -H "Accept: application/json" -H "Cache-Control: no-cache" "http://localhost:3000/api/v1/users/badMofos" 25 | ``` 26 | #### Use the telnet command and paste in the following commands to your terminal and hit enter. 27 | ```sh 28 | telnet localhost 3000 29 | ``` 30 | 31 | Paste this GET request into terminal that is expecting request and line feed and hit enter twice 32 | ```sh 33 | GET /api/v1/users/badMofos HTTP/1.1 34 | Host: localhost:3000 35 | Accept: application/json 36 | Cache-Control: no-cache 37 | Postman-Token: f3413251-c0de-69ac-99dd-992bcaaca3bd 38 | ``` 39 | 40 | #### Use a REST client such as Postman Chrome App or anything else. 41 | 42 | ##### Whichever way you use choose you get the following JSON payload 43 | ```json 44 | { 45 | "_id": "users", 46 | "_rev": "1-c9d988323eed080b054d6eb467abe4f9", 47 | "names": [ 48 | "John J Rambo", 49 | "Conan The Barbarian", 50 | "Billy Jack" 51 | ], 52 | "ranks": [ 53 | "One Bad Mofo", 54 | "Too Big of a dude", 55 | "Kicks too high for my taste" 56 | ] 57 | } 58 | ``` 59 | 60 | * Add assertion to expect function call in line 16 using the payload information. 61 | 62 | ### 2. Finish Integration Test for /api/v1/couch/insertDocument endpoint 63 | 64 | *The Super Test library api docs can be found here [SuperTest](https://visionmedia.github.io/superagent)* 65 | 66 | ####Hints to complete the exercise: 67 | *1. Use post method in supertest* 68 | *2. Use set method in supertest and pass in object with Accept and Content-Type headers.* 69 | *3. Use send method in supertest and pass in object with a name and document property.* 70 | *4. Make sure to call expect in supertest to do assertion and use previous get request as example.* 71 | *5. Make sure to end supertest call with end function call or the integration test won't finish.* 72 | *6. Check statuscode with SuperTest property [Status](https://visionmedia.github.io/superagent/#response-status)* 73 | *7. Use assertion methods from Tape [Asserts](http://www.node-tap.org/asserts)* 74 | *8. CouchDB api documentation [Docs](https://wiki.apache.org/couchdb/HTTP_Document_API)* 75 | *9. CouchDB driver for node.js (nano) that I am using [Docs](https://github.com/dscape/nano)* 76 | 77 | #### Making Rest Call with Curl for Post Request 78 | ```sh 79 | curl -X POST -H "Accept: application/json" -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{ 80 | "name": "some-document", 81 | "document": { 82 | "values": { 83 | "one": 1 84 | } 85 | } 86 | }' "http://localhost:3000/api/v1/couch/insertDocument" 87 | ``` 88 | 89 | ### 3. Write an integration test removing the newly created document to /api/v1/couch/deleteDocument/:name 90 | 91 | *The Super Test library api docs and rest methods can be found here [SuperTest](https://visionmedia.github.io/superagent/#request-basics)* 92 | 93 | ####Hints to complete the exercise: 94 | *1. Use appropriate supertest method to remove document.* 95 | *2. Make assertion with the returned status code (204) is usual status for DELETE request.* 96 | 97 | #### Making Rest Call with Curl for Delete Request 98 | ```sh 99 | curl -X DELETE -H "Content-Type: application/json" -d '{ 100 | "name": "spicegirls" 101 | }' "http://localhost:3000/api/v1/couch/deleteDocument" 102 | ``` 103 | -------------------------------------------------------------------------------- /integration-tests/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const request = require('supertest'); 3 | 4 | const app = require('../app'); 5 | 6 | test('Practice Integration Testing with a simple Node/Express API', nest => { 7 | 8 | // TODO: Make passing integration test by giving expected variable 9 | // GO to models/users.js and look at JSON structure. 10 | nest.test('Test /api/v1/users/badMofos endpoint', assert => { 11 | // const expected = ...; TODO: add expected block here 12 | request(app) 13 | .get('/api/v1/users/badMofos') 14 | .set('Accept', 'application/json') 15 | .expect(res => { 16 | 17 | }) 18 | .end((err, res) => { 19 | assert.end(); 20 | }); 21 | }); 22 | 23 | // TODO: Finish integration test that makes a POST request to /api/v1/couch/insertDocument 24 | nest.test('Test /api/v1/couch/insertDocument endpoint', assert => { 25 | assert.end(); 26 | }); 27 | 28 | // TODO: Write Integration test that tests /api/v1/couch/deleteDocument endpoint 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /integration-tests/solution/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const request = require('supertest'); 3 | 4 | const app = require('../../app'); 5 | const statusCodes = require('../../constants/constants')["statusCodes"]; 6 | 7 | test('Practice Integration Testing with a Restful API', nest => { 8 | 9 | nest.test('GET request to /api/v1/users/badMofos', assert => { 10 | const ok = statusCodes["ok"]; 11 | const expected = [ 'John J Rambo', 'Conan The Barbarian', 'Billy Jack' ]; 12 | request(app) 13 | .get('/api/v1/users/badMofos') 14 | .set('Accept', 'application/json') 15 | .expect(res => { 16 | assert.equal(res.status, ok); 17 | const soldiers = res.body.names 18 | assert.deepEqual( 19 | soldiers, 20 | expected, 21 | `Should return [ 'John J Rambo', 'Conan The Barbarian', 'Billy Jack' ]` 22 | ); 23 | }) 24 | .end((err, res) => { 25 | assert.end(); 26 | }); 27 | }); 28 | 29 | nest.test('POST request to /api/v1/couch/insertDocument', assert => { 30 | const created = statusCodes["create"]; 31 | request(app) 32 | .post('/api/v1/couch/insertDocument') 33 | .set({ 34 | 'Accept': 'application/json', 35 | 'Content-Type': 'application/json' 36 | }) 37 | .send({ 38 | "name": "spicegirls", 39 | "document": { 40 | "girls": ["Posh Spice", "Scary Spice", "Baby Spice", "Sporty Spice", "Ginger Spice"], 41 | "songs": ["Wannabe", "Stop", "Viva Forever", "Spice up your life"] 42 | } 43 | }) 44 | .expect(res => { 45 | assert.equal(res.status, created); 46 | assert.ok(res.body); 47 | assert.ok(res.body._id); 48 | }) 49 | .end((err, res) => { 50 | assert.end(); 51 | }); 52 | }); 53 | 54 | nest.test('POST request to /api/v1/couch/insertDocument that checks Update Conflict', assert => { 55 | const conflict = statusCodes["conflict"]; 56 | request(app) 57 | .post('/api/v1/couch/insertDocument') 58 | .set({ 59 | 'Accept': 'application/json', 60 | 'Content-Type': 'application/json' 61 | }) 62 | .send({ 63 | "name": "spicegirls", 64 | "document": { 65 | "girls": ["Posh Spice", "Scary Spice", "Baby Spice", "Sporty Spice", "Ginger Spice"], 66 | "songs": ["Wannabe", "Stop", "Viva Forever", "Spice up your life"] 67 | } 68 | }) 69 | .expect(res => { 70 | assert.equal(res.status, conflict); 71 | assert.equal(res.body.name, 'Error'); 72 | assert.equal(res.body.reason, 'Document update conflict.'); 73 | }) 74 | .end((err, res) => { 75 | assert.end(); 76 | }); 77 | }); 78 | 79 | nest.test('Delete request to /api/v1/couch/deleteDocument', assert => { 80 | const deleted = statusCodes["delete"]; 81 | request(app) 82 | .del('/api/v1/couch/deleteDocument') 83 | .set({ 84 | 'Content-Type': 'application/json' 85 | }) 86 | .send({ 87 | "name": "spicegirls" 88 | }) 89 | .expect(res => { 90 | assert.equal(res.status, deleted); 91 | }) 92 | .end((err, res) => { 93 | assert.end(); 94 | }) 95 | }); 96 | 97 | nest.test('Delete request to /api/v1/couch/deleteDocument', assert => { 98 | const gone = statusCodes["gone"]; 99 | request(app) 100 | .del('/api/v1/couch/deleteDocument') 101 | .set({ 102 | 'Content-Type': 'application/json' 103 | }) 104 | .send({ 105 | "name": "spicegirls" 106 | }) 107 | .expect(res => { 108 | assert.equal(res.status, gone); 109 | assert.equal(res.body.message, "deleted"); 110 | }) 111 | .end((err, res) => { 112 | assert.end(); 113 | }) 114 | }); 115 | 116 | nest.test('POST request to /api/v1/couch/insertDocument', assert => { 117 | const created = statusCodes["create"]; 118 | request(app) 119 | .post('/api/v1/couch/insertDocument/movies') 120 | .set({ 121 | 'Accept': 'application/json', 122 | 'Content-Type': 'application/json' 123 | }) 124 | .send({ 125 | "document": { 126 | "movies": ["Berry Gordy's Last Dragon", "Predator", "Rocky", "Rambo", "Dude Where's My Car"], 127 | "ratings": [ 128 | { 129 | "rating": "5 Stars" 130 | }, 131 | { 132 | "rating": "4 Stars" 133 | }, 134 | { 135 | "rating": "5 Stars" 136 | }, 137 | { 138 | "rating": "5 Stars" 139 | }, 140 | { 141 | "rating": "2 Stars" 142 | } 143 | ] 144 | } 145 | }) 146 | .expect(res => { 147 | assert.equal(res.status, created); 148 | assert.ok(res.body); 149 | assert.ok(res.body._id); 150 | assert.ok(res.body._id, "movies"); 151 | }) 152 | .end((err, res) => { 153 | assert.end(); 154 | }); 155 | }); 156 | 157 | nest.test('Delete request to /api/v1/couch/deleteDocument/:name', assert => { 158 | const deleted = statusCodes["delete"]; 159 | request(app) 160 | .del('/api/v1/couch/deleteDocument/movies') 161 | .set({ 162 | 'Accept': 'application/json', 163 | 'Content-Type': 'application/json' 164 | }) 165 | .expect(res => { 166 | assert.equal(res.status, deleted); 167 | }) 168 | .end((err, res) => { 169 | assert.end(); 170 | }) 171 | }); 172 | 173 | }); -------------------------------------------------------------------------------- /mocks-stubs-spies/instructions.md: -------------------------------------------------------------------------------- 1 | ## Mocks 2 | 3 | To view lecture notes for this course, please consult the [github-pages](https://jbelmont.github.io/software-testing). 4 | 5 | #### Definition of Mocks via Wikipedia [Mocks](https://en.wikipedia.org/wiki/Mock_object) 6 | In object-oriented programming, mock objects (also can be a unit of work) are simulated objects that mimic the behavior of real objects in controlled ways. A programmer typically creates a mock object to test the behavior of some other object, in much the same way that a car designer uses a crash test dummy to simulate the dynamic behavior of a human in vehicle impacts. 7 | 8 | #### Reasons to use Mock Objects 9 | In a unit test, mock objects can simulate the behavior of complex, real objects and are therefore useful when a real object is impractical or impossible to incorporate into a unit test. If an actual object has any of the following characteristics, it may be useful to use a mock object in its place: 10 | the object supplies non-deterministic results (e.g. the current time or the current temperature); 11 | 12 | **The Object has states that are difficult to create or reproduce (e.g. a network error);** 13 | **The Object is slow (e.g. a complete database, which would have to be initialized before the test);** 14 | **The Object does not yet exist or may change behavior;** 15 | **The Object would have to include information and methods exclusively for testing purposes (and not for its actual task).** 16 | 17 | #### Sinon.js Mock via explanation [Sinon Mocks](http://sinonjs.org/docs/#mocks) 18 | Mocks (and mock expectations) are fake methods (like spies) with pre-programmed behavior (like stubs) as well as pre-programmed expectations. 19 | A mock will fail your test if it is not used as expected. 20 | 21 | Sinon Documentation discusses when not to use Mocks. 22 | Mocks come with built-in expectations that may fail your test. Thus, they enforce implementation details. 23 | **The rule of thumb is: if you wouldn’t add an assertion for some specific call, don’t mock it. Use a stub instead.** 24 | **In general you should never have more than one mock (possibly with several expectations) in a single test.** 25 | 26 | #### Sinon.js Stubs via explanation [Sinon Stubs](http://sinonjs.org/docs/#stubs) 27 | Test stubs are functions (spies) with pre-programmed behavior. They support the full test spy API in addition to methods which can be used to alter the stub’s behavior. 28 | This is a key point here as well with stubs you get the full spy api but with Mocks you don't. 29 | 30 | #### Sinon.js Spies via explanation [Sinon Spies](http://sinonjs.org/docs/#spies) 31 | A test spy is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls. 32 | Test spies are useful to test both callbacks and how certain functions/methods are used throughout the system under test. 33 | 34 | *Sinon has an assertion api that you can reference here [Sinon Assertions](http://sinonjs.org/docs/#assertions)* 35 | *You can use either Mocha or Tape.js here it is your choice which one you feel most comfortable with.* 36 | 37 | #### Sinon has an assertion library that you can use but I would recommend using either chai.js assertion library or should.js 38 | * Chai.js documentation can be found here [Chai.js Assert](http://chaijs.com/api/assert/) // [Chai.js BDD](http://chaijs.com/api/bdd/) 39 | * Should.js Assertion library can be found here [Should.js](http://shouldjs.github.io/) 40 | 41 | **Open program.test.js in `mocks-stubs-spies` folder** 42 | 43 | #### 1. Stub the `retrieveDocument` function 44 | ```javascript 45 | function retrieveDocument({dbName, name}) { 46 | const couchDBName = nano.use(dbName); 47 | return new Promise((resolve, reject) => { 48 | couchDBName.get(name, (err, body) => { 49 | if (!err) { 50 | resolve(body); 51 | } 52 | reject(err); 53 | }); 54 | }); 55 | } 56 | ``` 57 | 58 | You don't need the implementation here but I included it here for your reference. 59 | Using Sinon check that the retrieveDocument stub is called once. 60 | Make an assertion that the payload and the expected response match. 61 | 62 | #### 2. Stub the `insertDocument` function. 63 | 64 | ```javascript 65 | function insertDocument({ dbName = 'softwaretesting', name = 'users', body } = {}) { 66 | return new Promise((resolve, reject) => { 67 | const couchDBName = nano.use(dbName); 68 | return insertDoc({ dbName: couchDBName, name, body }) 69 | .then(() => { 70 | resolve(retrieveDoc({ dbName: couchDBName , name })); 71 | }) 72 | .catch(err => { 73 | reject(err); 74 | }); 75 | }); 76 | } 77 | 78 | function insertDoc({dbName, name, body}) { 79 | return new Promise((resolve, reject) => { 80 | dbName.insert(body, name, (err, body, header) => { 81 | if (!err) { 82 | resolve(body); 83 | } else { 84 | reject(err); 85 | } 86 | }); 87 | }); 88 | } 89 | ``` 90 | 91 | *Again you don't need the implementation here because you are stubbing it out but I added it in case you are curious.* 92 | 93 | * Use sinon to make some assertions about the stubbed out function 94 | * Remember to use the setup function in tape or the before block in mocha to initialize the stub. 95 | 96 | #### 3. Stub out the `deleteDocument` function 97 | 98 | ###### Implementation Details 99 | ```javascript 100 | function retrieveDoc({dbName, name}) { 101 | return new Promise((resolve, reject) => { 102 | dbName.get(name, (err, body) => { 103 | if (!err) { 104 | resolve(body); 105 | } 106 | reject(err); 107 | }); 108 | }); 109 | } 110 | 111 | function deleteDocument({dbName, name}) { 112 | const couchDBName = nano.use(dbName); 113 | return retrieveDoc({dbName: couchDBName, name}) 114 | .then(body => { 115 | if (body) { 116 | const { 117 | _rev 118 | } = body; 119 | couchDBName.destroy(name, _rev, (err, body) => { 120 | if (!err) { 121 | return body; 122 | } 123 | throw err; 124 | }); 125 | } 126 | }); 127 | } 128 | ``` 129 | 130 | * Stub out the deleteDocument function by using sinon. 131 | * Use chai assertions or use the should.js assertion library 132 | -------------------------------------------------------------------------------- /mocks-stubs-spies/program.tape.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const test = require('tape'); 4 | const {join} = require('path'); 5 | const sinon = require('sinon'); 6 | const should = require('should'); 7 | 8 | const statusCodes = require('../constants/constants')["statusCodes"]; 9 | const DocOperations = require(join(__dirname, '../models/crudOperations')); 10 | 11 | let sandbox; 12 | let retrieveDocStub, insertDocStub, deleteDocStub; 13 | test('setup', t => { 14 | sandbox = sinon.sandbox.create(); 15 | // TODO: setup work here. 16 | t.end(); 17 | }); 18 | 19 | // TODO: finish the tests here. 20 | test('Practice Testing with Mocks, Spies, Stubs using Sinon', nest => { 21 | nest.test('Stub out retrieveDocuments by using sinon.stub and spy with sinon.assert', assert => { 22 | assert.end(); 23 | }); 24 | 25 | nest.test('Stub out insertDocument and test calling sinon.stub with different arguments', assert => { 26 | assert.end(); 27 | }); 28 | 29 | nest.test('Stub out deleteDocument and test calling sinon.stub multiple times', assert => { 30 | assert.end(); 31 | }); 32 | }); 33 | 34 | test('teardown', t => { 35 | sandbox.restore(); 36 | t.end(); 37 | }); -------------------------------------------------------------------------------- /mocks-stubs-spies/program.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const {join} = require('path'); 4 | const chai = require('chai'); 5 | const expect = chai.expect; 6 | const should = chai.should(); 7 | const sinon = require('sinon'); 8 | 9 | const DocOperations = require(join(__dirname, '../models/crudOperations')); 10 | const statusCodes = require('../constants/constants')["statusCodes"]; 11 | 12 | const sandbox = sinon.sandbox.create(); 13 | let retrieveDocStub, insertDocStub, deleteDocStub; 14 | describe('Practice Using Mocks, Stubs, and Spies using sinon', () => { 15 | 16 | before(() => { 17 | // TODO: Add setup here 18 | }); 19 | 20 | after(() => { 21 | sandbox.restore(); 22 | }); 23 | 24 | describe('Stub out all the couchDB calls with sinon mocking library.', () => { 25 | 26 | it('Stub out retrieveDocuments by using sinon.stub and spy with sinon.assert', done => { 27 | done(); 28 | }); 29 | 30 | it('Stub out insertDocument and test calling sinon.stub with different arguments', done => { 31 | done(); 32 | }); 33 | 34 | it('Stub out deleteDocument and test calling sinon.stub multiple times', done => { 35 | done(); 36 | }); 37 | }); 38 | 39 | }); -------------------------------------------------------------------------------- /mocks-stubs-spies/solution/sinon/program.tape.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const test = require('tape'); 4 | const {join} = require('path'); 5 | const sinon = require('sinon'); 6 | 7 | const statusCodes = require('../../../constants/constants')["statusCodes"]; 8 | const DocOperations = require(join(__dirname, '../../../models/crudOperations')); 9 | 10 | let sandbox; 11 | let retrieveDocStub, insertDocStub, deleteDocStub; 12 | test('setup', t => { 13 | sandbox = sinon.sandbox.create(); 14 | retrieveDocStub = sandbox.stub(DocOperations, "retrieveDocument").returns({ 15 | "_id": "heman", 16 | "_rev": "1-f250259ef8e8c1e6f98734110999246f", 17 | "heroSlogan": "I have the power", 18 | "He-Man": [ 19 | "He-Man", 20 | "Battle Cat", 21 | "Man-At-Arms", 22 | "Teela", 23 | "Skeletor" 24 | ] 25 | }); 26 | 27 | insertDocStub = sandbox.stub(DocOperations, "insertDocument"); 28 | insertDocStub.withArgs().throws({ 29 | "err": { 30 | "message": "400", 31 | "stack": "Error: 400\n some file somewhere" 32 | }, 33 | "message": "Missing name/document." 34 | }); 35 | insertDocStub.withArgs({ 36 | "document": { 37 | "marvel": "A bunch of characters", 38 | "characters": [ 39 | "Spider Man", 40 | "Thor", 41 | "Incredible Hulk", 42 | "Superman", 43 | "Batman" 44 | ] 45 | } 46 | }).returns({ 47 | "_id": "heman", 48 | "_rev": "9-cf3fae66dc4bc00b7c16a70a087e2f7b", 49 | "heroSlogan": "I have the power", 50 | "He-Man": [ 51 | "He-Man", 52 | "Battle Cat", 53 | "Man-At-Arms", 54 | "Teela", 55 | "Skeletor" 56 | ] 57 | }); 58 | 59 | deleteDocStub = sandbox.stub(DocOperations, "deleteDocument"); 60 | deleteDocStub.onCall(0).returns(); 61 | deleteDocStub.onCall(1).returns({ 62 | "message": "deleted", 63 | "stack": "Error: deleted\n stuff", 64 | "name": "Error", 65 | "error": "not_found", 66 | "reason": "deleted", 67 | "scope": "couch", 68 | "statusCode": 404, 69 | "request": { 70 | "method": "GET", 71 | "headers": { 72 | "content-type": "application/json", 73 | "accept": "application/json" 74 | }, 75 | "uri": "http://127.0.0.1:5984/softwaretesting/bob" 76 | }, 77 | "headers": { 78 | "date": "Date 1", 79 | "content-type": "application/json", 80 | "cache-control": "must-revalidate", 81 | "statusCode": 404, 82 | "uri": "http://127.0.0.1:5984/softwaretesting/bob" 83 | }, 84 | "errid": "non_200", 85 | "description": "couch returned 404" 86 | }); 87 | t.end(); 88 | }); 89 | 90 | test('Practice Testing with Mocks, Spies, Stubs using Sinon', nest => { 91 | nest.test('Stub out retrieveDocuments by using sinon.stub and spy with sinon.assert', assert => { 92 | const heMan = [ 93 | "He-Man", 94 | "Battle Cat", 95 | "Man-At-Arms", 96 | "Teela", 97 | "Skeletor" 98 | ]; 99 | const heManDoc = DocOperations.retrieveDocument({ 100 | dbName: 'softwaretesting', 101 | name: 'powerRangers' 102 | }); 103 | sinon.assert.calledOnce(retrieveDocStub); 104 | assert.deepEqual(heManDoc["He-Man"], heMan, "He-Man should have his proper foes and heroes."); 105 | assert.end(); 106 | }); 107 | 108 | nest.test('Stub out insertDocument and test calling sinon.stub with different arguments', assert => { 109 | try { 110 | const insertDocCall1 = insertDocStub(); 111 | } catch (e) { 112 | sinon.assert.threw(insertDocStub); 113 | } 114 | const insertDoc = { 115 | "document": { 116 | "marvel": "A bunch of characters", 117 | "characters": [ 118 | "Spider Man", 119 | "Thor", 120 | "Incredible Hulk", 121 | "Superman", 122 | "Batman" 123 | ] 124 | } 125 | }; 126 | const insertDocCall2 = insertDocStub(insertDoc); 127 | sinon.assert.calledWith(insertDocStub, insertDoc); 128 | assert.end(); 129 | }); 130 | 131 | nest.test('Stub out deleteDocument and test calling sinon.stub multiple times', assert => { 132 | const deleteDoc1 = deleteDocStub(); 133 | assert.equal(deleteDoc1, undefined); 134 | const deleteDoc2 = deleteDocStub(); 135 | sinon.assert.calledTwice(deleteDocStub); 136 | assert.ok(deleteDoc2["error"], "Should return an error when trying to delete document again"); 137 | assert.end(); 138 | }); 139 | }); 140 | 141 | test('teardown', t => { 142 | sandbox.restore(); 143 | t.end(); 144 | }); 145 | -------------------------------------------------------------------------------- /mocks-stubs-spies/solution/sinon/program.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const {join} = require('path'); 4 | const chai = require('chai'); 5 | const expect = chai.expect; 6 | const should = require('should'); 7 | const sinon = require('sinon'); 8 | const assert = require('chai').assert; 9 | 10 | const DocOperations = require(join(__dirname, '../../../models/crudOperations')); 11 | const statusCodes = require('../../../constants/constants')["statusCodes"]; 12 | 13 | const sandbox = sinon.sandbox.create(); 14 | let retrieveDocStub, insertDocStub, deleteDocStub; 15 | describe('Practice Using Mocks, Stubs, and Spies using sinon', () => { 16 | 17 | before(() => { 18 | retrieveDocStub = sandbox.stub(DocOperations, "retrieveDocument").returns({ 19 | "_id": "heman", 20 | "_rev": "1-f250259ef8e8c1e6f98734110999246f", 21 | "heroSlogan": "I have the power", 22 | "He-Man": [ 23 | "He-Man", 24 | "Battle Cat", 25 | "Man-At-Arms", 26 | "Teela", 27 | "Skeletor" 28 | ] 29 | }); 30 | 31 | insertDocStub = sandbox.stub(DocOperations, "insertDocument"); 32 | insertDocStub.withArgs().throws({ 33 | "err": { 34 | "message": "400", 35 | "stack": "Error: 400\n some file somewhere" 36 | }, 37 | "message": "Missing name/document." 38 | }); 39 | insertDocStub.withArgs({ 40 | "document": { 41 | "marvel": "A bunch of characters", 42 | "characters": [ 43 | "Spider Man", 44 | "Thor", 45 | "Incredible Hulk", 46 | "Superman", 47 | "Batman" 48 | ] 49 | } 50 | }).returns({ 51 | "_id": "heman", 52 | "_rev": "9-cf3fae66dc4bc00b7c16a70a087e2f7b", 53 | "heroSlogan": "I have the power", 54 | "He-Man": [ 55 | "He-Man", 56 | "Battle Cat", 57 | "Man-At-Arms", 58 | "Teela", 59 | "Skeletor" 60 | ] 61 | }); 62 | 63 | deleteDocStub = sandbox.stub(DocOperations, "deleteDocument"); 64 | deleteDocStub.onCall(0).returns(); 65 | deleteDocStub.onCall(1).returns({ 66 | "message": "deleted", 67 | "stack": "Error: deleted\n stuff", 68 | "name": "Error", 69 | "error": "not_found", 70 | "reason": "deleted", 71 | "scope": "couch", 72 | "statusCode": 404, 73 | "request": { 74 | "method": "GET", 75 | "headers": { 76 | "content-type": "application/json", 77 | "accept": "application/json" 78 | }, 79 | "uri": "http://127.0.0.1:5984/softwaretesting/bob" 80 | }, 81 | "headers": { 82 | "date": "Date 1", 83 | "content-type": "application/json", 84 | "cache-control": "must-revalidate", 85 | "statusCode": 404, 86 | "uri": "http://127.0.0.1:5984/softwaretesting/bob" 87 | }, 88 | "errid": "non_200", 89 | "description": "couch returned 404" 90 | }); 91 | }); 92 | 93 | after(() => { 94 | sandbox.restore(); 95 | }); 96 | 97 | describe('Stub out all the couchDB calls with sinon mocking library.', () => { 98 | 99 | it('Stub out retrieveDocuments by using sinon.stub and spy with sinon.assert', done => { 100 | const heMan = [ 101 | "He-Man", 102 | "Battle Cat", 103 | "Man-At-Arms", 104 | "Teela", 105 | "Skeletor" 106 | ]; 107 | const heManDoc = DocOperations.retrieveDocument({ 108 | dbName: 'softwaretesting', 109 | name: 'powerRangers' 110 | }); 111 | sinon.assert.calledOnce(retrieveDocStub); 112 | should.deepEqual(heManDoc["He-Man"], heMan, "He-Man should have his proper foes and heroes."); 113 | done(); 114 | }); 115 | 116 | it('Stub out insertDocument and test calling sinon.stub with different arguments', done => { 117 | try { 118 | const insertDocCall1 = insertDocStub(); 119 | } catch (e) { 120 | sinon.assert.threw(insertDocStub); 121 | } 122 | const insertDoc = { 123 | "document": { 124 | "marvel": "A bunch of characters", 125 | "characters": [ 126 | "Spider Man", 127 | "Thor", 128 | "Incredible Hulk", 129 | "Superman", 130 | "Batman" 131 | ] 132 | } 133 | }; 134 | const insertDocCall2 = insertDocStub(insertDoc); 135 | sinon.assert.calledWith(insertDocStub, insertDoc); 136 | done(); 137 | }); 138 | 139 | it('Stub out deleteDocument and test calling sinon.stub multiple times', done => { 140 | const deleteDoc1 = deleteDocStub(); 141 | should.equal(deleteDoc1, undefined); 142 | const deleteDoc2 = deleteDocStub(); 143 | sinon.assert.calledTwice(deleteDocStub); 144 | should.ok(deleteDoc2["error"], "Should return an error when tryingn to delete document again"); 145 | done(); 146 | }); 147 | }); 148 | 149 | }); 150 | -------------------------------------------------------------------------------- /models/crudOperations.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const nano = require('nano')('http://127.0.0.1:5984/'); 4 | const winston = require('winston'); 5 | 6 | function insertDocument({ dbName = 'softwaretesting', name = 'users', body } = {}) { 7 | return new Promise((resolve, reject) => { 8 | const couchDBName = nano.use(dbName); 9 | return insertDoc({ dbName: couchDBName, name, body }) 10 | .then(() => { 11 | resolve(retrieveDoc({ dbName: couchDBName , name })); 12 | }) 13 | .catch(err => { 14 | reject(err); 15 | }); 16 | }); 17 | } 18 | 19 | function insertDoc({dbName, name, body}) { 20 | return new Promise((resolve, reject) => { 21 | dbName.insert(body, name, (err, body, header) => { 22 | if (!err) { 23 | resolve(body); 24 | } else { 25 | reject(err); 26 | } 27 | }); 28 | }); 29 | } 30 | 31 | function retrieveDoc({dbName, name}) { 32 | return new Promise((resolve, reject) => { 33 | dbName.get(name, (err, body) => { 34 | if (!err) { 35 | resolve(body); 36 | } 37 | reject(err); 38 | }); 39 | }); 40 | } 41 | 42 | function retrieveDocument({dbName, name}) { 43 | const couchDBName = nano.use(dbName); 44 | return new Promise((resolve, reject) => { 45 | couchDBName.get(name, (err, body) => { 46 | if (!err) { 47 | resolve(body); 48 | } 49 | reject(err); 50 | }); 51 | }); 52 | } 53 | 54 | function retrieveDoc({dbName, name}) { 55 | return new Promise((resolve, reject) => { 56 | dbName.get(name, (err, body) => { 57 | if (!err) { 58 | resolve(body); 59 | } 60 | reject(err); 61 | }); 62 | }); 63 | } 64 | 65 | function deleteDocument({dbName, name}) { 66 | const couchDBName = nano.use(dbName); 67 | return retrieveDoc({dbName: couchDBName, name}) 68 | .then(body => { 69 | if (body) { 70 | const { 71 | _rev 72 | } = body; 73 | couchDBName.destroy(name, _rev, (err, body) => { 74 | if (!err) { 75 | return body; 76 | } 77 | throw err; 78 | }); 79 | } 80 | }); 81 | } 82 | 83 | exports.retrieveDocument = retrieveDocument; 84 | exports.insertDocument = insertDocument; 85 | exports.deleteDocument = deleteDocument; 86 | -------------------------------------------------------------------------------- /models/db.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const nano = require('nano')('http://admin:password@db:5984'); 4 | const winston = require('winston'); 5 | 6 | const usermodels = require('./userModels'); 7 | 8 | function createDbConnection({ dbName = 'softwaretesting', name = 'users' } = {}) { 9 | return new Promise((resolve, reject) => { 10 | nano.db.create(dbName, (err, body) => { 11 | if (!err) { 12 | const couchDBName = nano.use(dbName); 13 | return insertInitialDocument({ dbName: couchDBName, name }) 14 | .then(() => { 15 | resolve(retrieveDocument({ dbName: couchDBName , name })); 16 | }) 17 | .catch(err => { 18 | winston.error(err); 19 | }); 20 | } else { 21 | const db = nano.use(dbName); 22 | resolve(retrieveDocument({ dbName: db, name })); 23 | } 24 | }); 25 | }); 26 | } 27 | 28 | function retrieveDocument({dbName, name}) { 29 | return new Promise((resolve, reject) => { 30 | dbName.get(name, (err, body) => { 31 | if (!err) { 32 | resolve(body); 33 | } else { 34 | winston.error(err); 35 | reject(err); 36 | } 37 | }); 38 | }); 39 | } 40 | 41 | function insertInitialDocument({dbName, name}) { 42 | return new Promise((resolve, reject) => { 43 | dbName.insert(usermodels, name, (err, body, header) => { 44 | if (!err) { 45 | winston.info(`Created ${name} table: ${usermodels}`); 46 | resolve(body); 47 | } else { 48 | reject(usermodels); 49 | } 50 | }); 51 | }); 52 | } 53 | 54 | function dbActions({ dbName = 'softwaretesting', name = 'users' } = {}) { 55 | return createDbConnection({dbName, name}); 56 | } 57 | 58 | exports.dbActions = dbActions; 59 | -------------------------------------------------------------------------------- /models/userModels.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | usersModel: [ 3 | {"id":1,"first_name":"Timothy","last_name":"Cox","email":"tcox0@dion.ne.jp","gender":"Male"}, 4 | {"id":2,"first_name":"Sean","last_name":"Medina","email":"smedina1@addthis.com","gender":"Male"}, 5 | {"id":3,"first_name":"Jonathan","last_name":"Tucker","email":"jtucker2@tripadvisor.com","gender":"Male"}, 6 | {"id":4,"first_name":"Donna","last_name":"Payne","email":"dpayne3@cdbaby.com","gender":"Female"}, 7 | {"id":5,"first_name":"Emily","last_name":"Elliott","email":"eelliott4@pen.io","gender":"Female"}, 8 | {"id":6,"first_name":"Howard","last_name":"Wallace","email":"hwallace5@latimes.com","gender":"Male"}, 9 | {"id":7,"first_name":"Jacqueline","last_name":"George","email":"jgeorge6@soup.io","gender":"Female"}, 10 | {"id":8,"first_name":"Heather","last_name":"Kelly","email":"hkelly7@hubpages.com","gender":"Female"}, 11 | {"id":9,"first_name":"Kenneth","last_name":"Fisher","email":"kfisher8@wunderground.com","gender":"Male"}, 12 | {"id":10,"first_name":"Joan","last_name":"Flores","email":"jflores9@icq.com","gender":"Female"}, 13 | {"id":11,"first_name":"Wanda","last_name":"Hunt","email":"whunta@linkedin.com","gender":"Female"}, 14 | {"id":12,"first_name":"Alice","last_name":"Gordon","email":"agordonb@mozilla.com","gender":"Female"}, 15 | {"id":13,"first_name":"Nicholas","last_name":"Harrison","email":"nharrisonc@fastcompany.com","gender":"Male"}, 16 | {"id":14,"first_name":"Timothy","last_name":"Hudson","email":"thudsond@bloomberg.com","gender":"Male"}, 17 | {"id":15,"first_name":"Nancy","last_name":"Lynch","email":"nlynche@163.com","gender":"Female"}, 18 | {"id":16,"first_name":"Ryan","last_name":"Stevens","email":"rstevensf@dedecms.com","gender":"Male"}, 19 | {"id":17,"first_name":"Shawn","last_name":"Little","email":"slittleg@cnet.com","gender":"Male"}, 20 | {"id":18,"first_name":"Frances","last_name":"Garrett","email":"fgarretth@cargocollective.com","gender":"Female"}, 21 | {"id":19,"first_name":"Brian","last_name":"Nelson","email":"bnelsoni@eepurl.com","gender":"Male"}, 22 | {"id":20,"first_name":"Harry","last_name":"Anderson","email":"handersonj@about.com","gender":"Male"}, 23 | {"id":21,"first_name":"Michelle","last_name":"Nelson","email":"mnelsonk@tiny.cc","gender":"Female"}, 24 | {"id":22,"first_name":"Scott","last_name":"Palmer","email":"spalmerl@canalblog.com","gender":"Male"}, 25 | {"id":23,"first_name":"Helen","last_name":"Day","email":"hdaym@geocities.com","gender":"Female"}, 26 | {"id":24,"first_name":"Aaron","last_name":"Torres","email":"atorresn@rediff.com","gender":"Male"}, 27 | {"id":25,"first_name":"Cheryl","last_name":"Morris","email":"cmorriso@theglobeandmail.com","gender":"Female"}, 28 | {"id":26,"first_name":"Heather","last_name":"Sims","email":"hsimsp@about.me","gender":"Female"}, 29 | {"id":27,"first_name":"Andrew","last_name":"Morales","email":"amoralesq@cbsnews.com","gender":"Male"}, 30 | {"id":28,"first_name":"Kevin","last_name":"Lane","email":"klaner@epa.gov","gender":"Male"}, 31 | {"id":29,"first_name":"Karen","last_name":"Perkins","email":"kperkinss@geocities.com","gender":"Female"}, 32 | {"id":30,"first_name":"Jane","last_name":"Jackson","email":"jjacksont@icq.com","gender":"Female"}, 33 | {"id":31,"first_name":"Roy","last_name":"Green","email":"rgreenu@csmonitor.com","gender":"Male"}, 34 | {"id":32,"first_name":"Louis","last_name":"Berry","email":"lberryv@so-net.ne.jp","gender":"Male"}, 35 | {"id":33,"first_name":"Donald","last_name":"Kennedy","email":"dkennedyw@umich.edu","gender":"Male"}, 36 | {"id":34,"first_name":"Edward","last_name":"Schmidt","email":"eschmidtx@seattletimes.com","gender":"Male"}, 37 | {"id":35,"first_name":"Brenda","last_name":"Bennett","email":"bbennetty@cargocollective.com","gender":"Female"}, 38 | {"id":36,"first_name":"Bonnie","last_name":"Carr","email":"bcarrz@desdev.cn","gender":"Female"}, 39 | {"id":37,"first_name":"Tammy","last_name":"Bailey","email":"tbailey10@technorati.com","gender":"Female"}, 40 | {"id":38,"first_name":"Peter","last_name":"Murray","email":"pmurray11@mozilla.com","gender":"Male"}, 41 | {"id":39,"first_name":"Kathryn","last_name":"Peterson","email":"kpeterson12@bandcamp.com","gender":"Female"}, 42 | {"id":40,"first_name":"Linda","last_name":"Carter","email":"lcarter13@redcross.org","gender":"Female"}, 43 | {"id":41,"first_name":"Scott","last_name":"Howell","email":"showell14@sogou.com","gender":"Male"}, 44 | {"id":42,"first_name":"Lillian","last_name":"Nichols","email":"lnichols15@a8.net","gender":"Female"}, 45 | {"id":43,"first_name":"Frank","last_name":"Wells","email":"fwells16@google.es","gender":"Male"}, 46 | {"id":44,"first_name":"Jean","last_name":"Wheeler","email":"jwheeler17@mlb.com","gender":"Female"}, 47 | {"id":45,"first_name":"Phyllis","last_name":"Arnold","email":"parnold18@deliciousdays.com","gender":"Female"}, 48 | {"id":46,"first_name":"Irene","last_name":"Mills","email":"imills19@nhs.uk","gender":"Female"}, 49 | {"id":47,"first_name":"Rose","last_name":"Anderson","email":"randerson1a@google.pl","gender":"Female"}, 50 | {"id":48,"first_name":"Harry","last_name":"Little","email":"hlittle1b@google.fr","gender":"Male"}, 51 | {"id":49,"first_name":"Ruby","last_name":"Rogers","email":"rrogers1c@digg.com","gender":"Female"}, 52 | {"id":50,"first_name":"Jonathan","last_name":"Carpenter","email":"jcarpenter1d@furl.net","gender":"Male"} 53 | ] 54 | }; -------------------------------------------------------------------------------- /models/users.js: -------------------------------------------------------------------------------- 1 | const users = { 2 | names: ['John J Rambo', 'Conan The Barbarian', 'Billy Jack'], 3 | ranks: ['One Bad Mofo', 'Too Big of a dude', 'Kicks too high for my taste'] 4 | }; 5 | 6 | exports.users = users; -------------------------------------------------------------------------------- /property-tests/program.test.js: -------------------------------------------------------------------------------- 1 | const { check, gen, property } = require('testcheck'); 2 | const test = require('tape'); 3 | 4 | test('addition is commutative', check(gen.int, gen.int, (t, numA, numB) => { 5 | t.plan(1); 6 | t.equal(numA + numB, numB + numA) 7 | })); 8 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | const router = express.Router(); 5 | const {join} = require('path'); 6 | const winston = require('winston'); 7 | 8 | const db = require(join(__dirname, '../models/db')); 9 | 10 | db.dbActions() 11 | .then(values => { 12 | const data = { 13 | users: JSON.stringify(values["usersModel"]) 14 | }; 15 | 16 | /* GET home page. */ 17 | router.get('/', function(req, res, next) { 18 | res.render('index', data); 19 | }); 20 | }); 21 | 22 | module.exports = router; -------------------------------------------------------------------------------- /static/build/software-testing.css: -------------------------------------------------------------------------------- 1 | #reduxWorkshopContainer{padding:5%}#reduxWorkshopContainer .code-craftsmanship-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}#reduxWorkshopContainer .code-craftsmanship-container-label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;height:5em;background:#2d2d2d !important;margin-bottom:2%}#reduxWorkshopContainer .code-craftsmanship-container-label>strong{color:#61dafb !important;font-weight:400 !important;font-size:3.75rem}#reduxWorkshopContainer .code-craftsmanship-container .add-user-btn-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}#reduxWorkshopContainer .code-craftsmanship-container .add-user-btn{width:12rem;height:3rem;font-size:1.5rem;font-weight:500} 2 | 3 | 4 | .users-container,.user-details-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center;height:2.5em;cursor:pointer}.users-container:hover,.user-details-container:hover{background-color:#87b0d3}.users-container-email,.users-container-first-name,.users-container-last-name,.users-container-gender,.users-container-id,.user-details-container-email,.user-details-container-first-name,.user-details-container-last-name,.user-details-container-gender,.user-details-container-id{width:100%}.users-container-trash-bin{width:15rem;height:2rem} 5 | 6 | /*# sourceMappingURL=software-testing.css.map */ 7 | -------------------------------------------------------------------------------- /static/build/software-testing.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["software-testing.scss","userDetail.css","users.scss"],"names":[],"mappings":"AAAA,wBACI,UAAY,CADhB,sDAIQ,oBAAA,AACA,oBADA,AACA,aAAA,4BAAA,AAAuB,6BAAvB,AAAuB,0BAAvB,AAAuB,qBAAA,CAL/B,4DAQY,oBAAA,AACA,oBADA,AACA,aAAA,yBAAA,AACA,sBADA,AACA,mBAAA,wBAAA,AACA,qBADA,AACA,uBAAA,WACA,8BACA,gBAAkB,CAb9B,mEAgBgB,yBACA,2BACA,iBAAmB,CAlBnC,8EAuBY,oBAAA,AACA,oBADA,AACA,aAAA,yBAAA,AACA,sBADA,AACA,mBAAA,qBAAA,AAA0B,kBAA1B,AAA0B,wBAAA,CAzBtC,oEA6BY,YACA,YACA,iBACA,eAAiB,CACpB;;ACjCT;ACAA,yCACI,oBAAA,AACA,oBADA,AACA,aAAA,yBAAA,AACA,sBADA,AACA,mBAAA,wBAAA,AACA,qBADA,AACA,uBAAA,kBACA,aACA,cAAgB,CANpB,qDASQ,wBAA0B,CATlC,uRAiBQ,UAAY,CACf,2BAIH,YACA,WAAa,CACd","file":"software-testing.css","sourcesContent":["#reduxWorkshopContainer {\n padding: 5%;\n\n .code-craftsmanship-container {\n display: flex;\n flex-direction: column;\n\n &-label {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 5em;\n background: #2d2d2d !important;\n margin-bottom: 2%;\n\n > strong {\n color: #61dafb !important;\n font-weight: 400 !important;\n font-size: 3.75rem;\n }\n }\n\n .add-user-btn-container {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n }\n\n .add-user-btn {\n width: 12rem;\n height: 3rem;\n font-size: 1.5rem;\n font-weight: 500;\n }\n }\n}",null,".users-container, .user-details-container {\n display: flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n height: 2.5em;\n cursor: pointer;\n\n &:hover {\n background-color: #87b0d3;\n }\n\n &-email,\n &-first-name,\n &-last-name,\n &-gender,\n &-id {\n width: 100%;\n }\n}\n\n.users-container-trash-bin {\n width: 15rem;\n height: 2rem;\n}\n"]} -------------------------------------------------------------------------------- /static/js/actions/index.js: -------------------------------------------------------------------------------- 1 | import * as constants from '../constants'; 2 | 3 | const { 4 | GET_USERS, 5 | USER_DETAIL_INFO, 6 | ADD_NEW_USER, 7 | REMOVE_USER 8 | } = constants; 9 | 10 | export function getusers({ users }) { 11 | return { 12 | type: GET_USERS, 13 | users 14 | }; 15 | } 16 | 17 | export function getUserInfo({ email, firstName, lastName, gender, id }) { 18 | return { 19 | type: USER_DETAIL_INFO, 20 | email, 21 | firstName, 22 | lastName, 23 | gender, 24 | id 25 | }; 26 | } 27 | 28 | export function addUserInfo({ email, firstName, lastName, gender, id }) { 29 | return { 30 | type: ADD_NEW_USER, 31 | email, 32 | firstName, 33 | lastName, 34 | gender, 35 | id 36 | }; 37 | } 38 | 39 | export function removeUser({ index }) { 40 | return { 41 | type: REMOVE_USER, 42 | index 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /static/js/components/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Router, Route, IndexRoute, hashHistory } from 'react-router'; 4 | import { Provider } from 'react-redux'; 5 | 6 | import store, { history } from '../store'; 7 | 8 | import CodeCraftsmanship from './CodeCraftsmanship.jsx'; 9 | import Main from './Main.jsx'; 10 | import UserDetails from './UserDetails.jsx'; 11 | 12 | const router = ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | 23 | render(router, document.getElementById('reduxWorkshopContainer')); 24 | -------------------------------------------------------------------------------- /static/js/components/Main.jsx: -------------------------------------------------------------------------------- 1 | import { bindActionCreators, getState } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import * as actionCreators from '../actions'; 4 | import CodeCraftsmanship from './CodeCraftsmanship.jsx'; 5 | 6 | function mapStateToProps(state) { 7 | return { 8 | users: state["users"] 9 | } 10 | } 11 | 12 | function mapDispatchToProps(dispatch) { 13 | return bindActionCreators(actionCreators, dispatch, getState); 14 | } 15 | 16 | const Main = connect(mapStateToProps, mapDispatchToProps)(CodeCraftsmanship); 17 | 18 | export default Main; 19 | -------------------------------------------------------------------------------- /static/js/components/UserDetails.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import {browserHistory} from 'react-router'; 3 | 4 | import store from '../store'; 5 | 6 | import * as constants from '../constants'; 7 | 8 | const UserDetails = routerInfo => { 9 | const { 10 | CODE_CRAFTSMANSHIP 11 | } = constants; 12 | const userInformation = store && store.getState() && store.getState()["userInfo"]; 13 | let email, firstName, lastName, gender, id; 14 | if (Object.keys(userInformation).length > 0) { 15 | email = userInformation["email"]; 16 | firstName = userInformation["firstName"]; 17 | lastName = userInformation["lastName"]; 18 | gender = userInformation["gender"]; 19 | id = userInformation["id"]; 20 | } else { 21 | const storeInfo = store && store.getState() && store.getState()["users"]; 22 | firstName = storeInfo["first_name"]; 23 | lastName = storeInfo["last_name"]; 24 | email = storeInfo["email"]; 25 | gender = storeInfo["gender"]; 26 | id = storeInfo["id"]; 27 | } 28 | 29 | const UserDetailsArea = ( 30 |
31 | {email} 32 | {firstName} 33 | {lastName} 34 | {gender} 35 |
36 | ); 37 | return ( 38 |
39 |

CODE_CRAFTSMANSHIP

40 | {UserDetailsArea} 41 |
42 | ); 43 | } 44 | 45 | export default UserDetails; 46 | -------------------------------------------------------------------------------- /static/js/components/Users.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import {browserHistory} from 'react-router'; 3 | 4 | const Users = ( { email, first_name, last_name, gender, id, key, index, props, trashBinSvgPath, store, onClick } ) => { 5 | const getUserInfo = element => { 6 | return { 7 | email: element.filter(elem => elem['dataset']['email'])[0].dataset['email'], 8 | firstName: element.filter(elem => elem['dataset']['firstName'])[0].dataset['firstName'], 9 | lastName: element.filter(elem => elem['dataset']['lastName'])[0].dataset['lastName'], 10 | gender: element.filter(elem => elem['dataset']['gender'])[0].dataset['gender'], 11 | id: element.filter(elem => elem['dataset']['id'])[0].dataset['id'] 12 | }; 13 | }; 14 | 15 | const remove = event => { 16 | event.stopPropagation(); 17 | event.preventDefault(); 18 | onClick[1]({index}); 19 | onClick[2]((store.getState()['users'])); 20 | }; 21 | 22 | const userDetail = event => { 23 | const userDetails = getUserInfo(Array.from(event.currentTarget.children)); 24 | onClick[0](userDetails); 25 | browserHistory.push({ 26 | pathname: `/user/${userDetails['id']}`, 27 | state: props 28 | }); 29 | }; 30 | 31 | return ( 32 |
userDetail(event)}> 33 | {email} 34 | {first_name} 35 | {last_name} 36 | {gender} 37 | {id} 38 | remove(event)} 42 | > 43 | 44 | 45 |
46 | ); 47 | }; 48 | 49 | Users.propTypes = { 50 | email: PropTypes.string, 51 | first_name: PropTypes.string, 52 | last_name: PropTypes.string, 53 | gender: PropTypes.string, 54 | id: PropTypes.string, 55 | key: PropTypes.string, 56 | index: PropTypes.string, 57 | props: PropTypes.array, 58 | trashBinSvgPath: PropTypes.string, 59 | store: PropTypes.func, 60 | onClick: PropTypes.func 61 | }; 62 | 63 | export default Users; 64 | -------------------------------------------------------------------------------- /static/js/constants/index.js: -------------------------------------------------------------------------------- 1 | // Redux Action Keys 2 | export const GET_USERS = 'USERS'; 3 | export const ADD_USER_DETAIL_INFO = 'ADD_USER_DETAIL_INFO'; 4 | export const ADD_NEW_USER = 'ADD_NEW_USER'; 5 | export const USER_DETAIL_INFO = 'USER_DETAIL_INFO'; 6 | export const REMOVE_USER = 'REMOVE_USER'; 7 | export const EMPTY_USER_INFO = 'EMPTY_USER_INFO'; 8 | 9 | // Component Text Values 10 | export const TRIANGLE_REACTJS_USERS = 'Triangle ReactJS Users'; 11 | export const CODE_CRAFTSMANSHIP = 'Code Craftsmanship Saturdays'; 12 | export const ADD_USER = 'Add User'; 13 | export const ADD = 'Add'; 14 | export const CLOSE = 'Close'; 15 | export const MALE = 'Male'; 16 | export const FEMALE = 'Female'; 17 | export const GENDER = 'Gender'; 18 | export const ADD_USERS_ENTRY = 'Add all the user information here.'; 19 | export const EMAIL = 'Email: '; 20 | export const FIRST_NAME = 'First Name: '; 21 | export const LAST_NAME = 'Last Name: '; 22 | 23 | // Image alt text 24 | 25 | // Text Data 26 | -------------------------------------------------------------------------------- /static/js/data/index.js: -------------------------------------------------------------------------------- 1 | const _users = JSON.parse(document.getElementById('userData').value); 2 | export default { 3 | users: _users 4 | }; 5 | -------------------------------------------------------------------------------- /static/js/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux'; 3 | 4 | import users from './users'; 5 | import userInfo from './userDetailInformation'; 6 | 7 | export const rootReducer = combineReducers({ 8 | users, 9 | userInfo, 10 | routing: routerReducer 11 | }); 12 | 13 | export default rootReducer; 14 | -------------------------------------------------------------------------------- /static/js/reducers/userDetailInformation.js: -------------------------------------------------------------------------------- 1 | function userInformation(state = [], action) { 2 | const { 3 | type, 4 | email, 5 | firstName, 6 | lastName, 7 | gender, 8 | id 9 | } = action; 10 | 11 | switch (type) { 12 | case 'USER_DETAIL_INFO': 13 | return Object.assign( 14 | {}, 15 | ...state, 16 | { 17 | email, 18 | firstName, 19 | lastName, 20 | gender, 21 | id 22 | }, 23 | ); 24 | case 'ADD_NEW_USER': 25 | return [ 26 | ...state, 27 | { 28 | email, 29 | first_name: firstName, 30 | last_name: lastName, 31 | gender, 32 | id 33 | } 34 | ]; 35 | case 'EMPTY_USER_INFO': 36 | return {}; 37 | default: 38 | return state; 39 | } 40 | } 41 | 42 | export default userInformation; 43 | -------------------------------------------------------------------------------- /static/js/reducers/users.js: -------------------------------------------------------------------------------- 1 | function users(state = [], action) { 2 | const { 3 | type, 4 | email, 5 | firstName, 6 | lastName, 7 | gender, 8 | index, 9 | id 10 | } = action; 11 | switch (type) { 12 | case 'ADD_NEW_USER': 13 | return [ 14 | ...state, 15 | { 16 | email, 17 | first_name: firstName, 18 | last_name: lastName, 19 | gender, 20 | id 21 | } 22 | ]; 23 | case 'REMOVE_USER': 24 | return [ 25 | ...state.slice(0, index !== 0 && (index - 1) || 0), 26 | ...state.slice(index !== 0 && index || 1, state.length) 27 | ]; 28 | default: 29 | return state; 30 | } 31 | } 32 | 33 | export default users; 34 | -------------------------------------------------------------------------------- /static/js/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import { syncHistoryWithStore} from 'react-router-redux'; 3 | import { browserHistory } from 'react-router'; 4 | 5 | import rootReducer from '../reducers'; 6 | import data from '../data'; 7 | 8 | const store = createStore(rootReducer, { users: data.users }); 9 | 10 | export const history = syncHistoryWithStore(browserHistory, store); 11 | 12 | export default store; 13 | -------------------------------------------------------------------------------- /static/js/utils/ajax.js: -------------------------------------------------------------------------------- 1 | export function ajax({type, route, body}) { 2 | return new Promise((resolve, reject) => { 3 | const request = new XMLHttpRequest(); 4 | request.open(type, route, true); 5 | if (type === "POST" || type === "PUT" && body) { 6 | request.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); 7 | request.setRequestHeader("Accept", "application/json"); 8 | } 9 | 10 | request.onload = function() { 11 | if (this.status >= 200 && this.status < 400) { 12 | const data = JSON.parse(this.response); 13 | resolve(data); 14 | } else { 15 | reject(new Error('error ocurred')); 16 | } 17 | }; 18 | 19 | request.onerror = function() { 20 | console.error('Something went wrong with the transaction'); 21 | }; 22 | 23 | if (body) { 24 | request.send(JSON.stringify(body)); 25 | } else { 26 | request.send(); 27 | } 28 | }); 29 | } -------------------------------------------------------------------------------- /static/scss/software-testing.scss: -------------------------------------------------------------------------------- 1 | #reduxWorkshopContainer { 2 | padding: 5%; 3 | 4 | .code-craftsmanship-container { 5 | display: flex; 6 | flex-direction: column; 7 | 8 | &-label { 9 | display: flex; 10 | align-items: center; 11 | justify-content: center; 12 | height: 5em; 13 | background: #2d2d2d !important; 14 | margin-bottom: 2%; 15 | 16 | > strong { 17 | color: #61dafb !important; 18 | font-weight: 400 !important; 19 | font-size: 3.75rem; 20 | } 21 | } 22 | 23 | .add-user-btn-container { 24 | display: flex; 25 | align-items: center; 26 | justify-content: flex-end; 27 | } 28 | 29 | .add-user-btn { 30 | width: 12rem; 31 | height: 3rem; 32 | font-size: 1.5rem; 33 | font-weight: 500; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /static/scss/userDetail.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/static/scss/userDetail.scss -------------------------------------------------------------------------------- /static/scss/users.scss: -------------------------------------------------------------------------------- 1 | .users-container, .user-details-container { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | text-align: center; 6 | height: 2.5em; 7 | cursor: pointer; 8 | 9 | &:hover { 10 | background-color: #87b0d3; 11 | } 12 | 13 | &-email, 14 | &-first-name, 15 | &-last-name, 16 | &-gender, 17 | &-id { 18 | width: 100%; 19 | } 20 | } 21 | 22 | .users-container-trash-bin { 23 | width: 15rem; 24 | height: 2rem; 25 | } 26 | -------------------------------------------------------------------------------- /tdd/instructions.md: -------------------------------------------------------------------------------- 1 | ## Test-Driven Development (BDD) 2 | 3 | To view lecture notes for this course, please consult the [github-pages](https://jbelmont.github.io/software-testing). 4 | 5 | #### Definition of TDD via Wikipedia [Test-Driven Development](https://en.wikipedia.org/wiki/Test-driven_development) 6 | 7 | Each test case fails initially: This ensures that the test really works and can catch an error. Once this is shown, the underlying functionality can be implemented. This has led to the "test-driven development mantra", which is "red/green/refactor", where red means fail and green means pass. Test-driven development constantly repeats the steps of adding test cases that fail, passing them, and refactoring. Receiving the expected test results at each stage reinforces the developer's mental model of the code, boosts confidence and increases productivity. 8 | 9 | #### Test-Driven Development Cycle 10 | 11 | **_1. Add a test_** 12 | **_2. Run all tests and see if the new test fails_** 13 | **_3. Write the code_** 14 | **_4. Run tests_** 15 | **_5. Refactor code._** 16 | **_Repeat_** 17 | 18 | #### Exercise Instructions 19 | Requirements for First TDD Cycle 20 | Create a function that computes the average of a range of numbers. 21 | Please go to slide xyz in [github-pages](https://jbelmont.github.io/software-testing) 22 | 23 | ##### Test-Driven Development Cycle 1 (Add a Test / Run Tests) 24 | 25 | 1. Go to file path `tdd/tdd-cycle/cycle1/program.test.js` and add a failing test by calling a function that doesn't exist in program.js 26 | 2. Add a failing test in program.test.js using either Mocha with Chai or with Tape from our previous exercies. 27 | 3. Run the failing test `npm run tdd:cycle1` 28 | 29 | ##### Test-Driven Development Cycle 2 (Write the Code / Run Tests) 30 | 31 | 1. Go to file path `tdd/tdd-cycle/cycle2`. 32 | 33 | 2. Add the minimal requirement to make the test pass again. 34 | 35 | 3. (Hint) Add an empty function in `program.js` and then call it with the appropriate assertion. 36 | 37 | 4. Run the test with `npm run tdd:cycle2` 38 | 39 | ##### Test-Driven Development Cycle 3 (Refactor by adding implementation / Add a test / Run all Tests again) 40 | 41 | 1. Go to file path `tdd/tdd-cycle/cycle3`. 42 | 43 | 2. Implement the average function in program.js. 44 | 45 | 3. Add a unit test for the average function with an array of numbers. 46 | 47 | 4. Use appropriate assertion to unit test the function. 48 | 49 | 5. Run the test with `npm run tdd:cycle3` 50 | 51 | ##### Test-Driven Development Cycle Final / (Refactor code / Add a test / Run all tests again) 52 | 53 | 1. Go to the file path `tdd/tdd-cycle/cyclefinal`. 54 | 55 | 2. Refactor the code again with possible different implementation or quit. 56 | 57 | 3. If refactored with newer function than add new test else add run the same test for original implementation 58 | 59 | 4. Run the test with `npm run tdd:cycle:final` 60 | -------------------------------------------------------------------------------- /tdd/solution/program.js: -------------------------------------------------------------------------------- 1 | const average = input => { 2 | return input.reduce((prev, curr) => prev + curr, 0) / input.length; 3 | }; 4 | 5 | const standardDeviation = input => { 6 | const avg = average(input); 7 | const summation = input 8 | .map(val => Math.pow(Math.abs(val - avg), 2)) 9 | .reduce((prev, curr) => prev + curr, 0) / input.length; 10 | return Math.sqrt(summation); 11 | }; 12 | 13 | module.exports = { 14 | average, 15 | standardDeviation 16 | }; -------------------------------------------------------------------------------- /tdd/solution/tdd-cycle/cycle1/program.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/tdd/solution/tdd-cycle/cycle1/program.js -------------------------------------------------------------------------------- /tdd/solution/tdd-cycle/cycle1/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | 3 | test('Practice the concept of test-driven development', nest => { 4 | 5 | nest.test('Unit test the average function', assert => { 6 | const tdd = require('./program'); 7 | assert.ok(tdd.average, "Average Function Doesn't Exist"); 8 | assert.end(); 9 | }); 10 | 11 | }); -------------------------------------------------------------------------------- /tdd/solution/tdd-cycle/cycle2/program.js: -------------------------------------------------------------------------------- 1 | function average() {} 2 | 3 | module.exports = { 4 | average 5 | } -------------------------------------------------------------------------------- /tdd/solution/tdd-cycle/cycle2/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | 3 | test('Practice the concept of test-driven development', nest => { 4 | 5 | nest.test('Does the average function exist', assert => { 6 | const tdd = require('./program'); 7 | assert.ok(tdd.average, "Average Function Does Exist"); 8 | assert.end(); 9 | }); 10 | 11 | }); -------------------------------------------------------------------------------- /tdd/solution/tdd-cycle/cycle3/program.js: -------------------------------------------------------------------------------- 1 | function average(input) { 2 | let sum = 0; 3 | for (let i = 0; i < input.length; i++) { 4 | sum += input[i]; 5 | } 6 | return sum / input.length; 7 | } 8 | 9 | module.exports = { 10 | average 11 | } -------------------------------------------------------------------------------- /tdd/solution/tdd-cycle/cycle3/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | 3 | test('Practice the concept of test-driven development', nest => { 4 | 5 | nest.test('Does the average function exist', assert => { 6 | const tdd = require('./program'); 7 | assert.ok(tdd.average, "Average Function Does Exist"); 8 | assert.end(); 9 | }); 10 | 11 | nest.test('Test the average function', assert => { 12 | const {average} = require('./program'); 13 | const expected = 5.5; 14 | assert.equal(average([1,2,3,4,5,6,7,8,9,10]), expected); 15 | assert.end(); 16 | }); 17 | 18 | nest.test('Test refactored average function', assert => { 19 | const {average} = require('./program'); 20 | const expected = 5.5; 21 | assert.equal(average([1,2,3,4,5,6,7,8,9,10]), expected); 22 | assert.end(); 23 | }); 24 | 25 | }); -------------------------------------------------------------------------------- /tdd/solution/tdd-cycle/cyclefinal/program.js: -------------------------------------------------------------------------------- 1 | function avg(input) { 2 | return input.reduce((prev, curr) => prev + curr, 0) / input.length; 3 | } 4 | 5 | function average(input) { 6 | let sum = 0; 7 | for (let i = 0; i < input.length; i++) { 8 | sum += input[i]; 9 | } 10 | return sum / input.length; 11 | } 12 | 13 | module.exports = { 14 | average, 15 | avg 16 | } -------------------------------------------------------------------------------- /tdd/solution/tdd-cycle/cyclefinal/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | 3 | test('Practice the concept of test-driven development', nest => { 4 | 5 | nest.test('Does the average function exist', assert => { 6 | const tdd = require('./program'); 7 | assert.ok(tdd.average, "Average Function Does Exist"); 8 | assert.end(); 9 | }); 10 | 11 | nest.test('Test the average function', assert => { 12 | const {average} = require('./program'); 13 | const expected = 5.5; 14 | assert.equal(average([1,2,3,4,5,6,7,8,9,10]), expected); 15 | assert.end(); 16 | }); 17 | 18 | nest.test('Test refactored average function', assert => { 19 | const {avg} = require('./program'); 20 | const expected = 5.5; 21 | assert.equal(avg([1,2,3,4,5,6,7,8,9,10]), expected); 22 | assert.end(); 23 | }); 24 | 25 | }); -------------------------------------------------------------------------------- /tdd/tdd-cycle/cycle1/program.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/tdd/tdd-cycle/cycle1/program.js -------------------------------------------------------------------------------- /tdd/tdd-cycle/cycle1/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | 3 | test('Practice the concept of test-driven development', nest => { 4 | 5 | nest.test('Unit test the average function', assert => { 6 | const tdd = require('./program'); 7 | assert.ok(tdd.average, "Average Function Doesn't Exist"); 8 | assert.end(); 9 | }); 10 | 11 | }); -------------------------------------------------------------------------------- /tdd/tdd-cycle/cycle2/program.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/tdd/tdd-cycle/cycle2/program.js -------------------------------------------------------------------------------- /tdd/tdd-cycle/cycle2/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | 3 | test('Practice the concept of test-driven development', nest => { 4 | 5 | nest.test('Unit test the average function', assert => { 6 | const tdd = require('./program'); 7 | assert.ok(tdd.average, "Average Function Doesn't Exist"); 8 | assert.end(); 9 | }); 10 | 11 | }); -------------------------------------------------------------------------------- /tdd/tdd-cycle/cycle3/program.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/tdd/tdd-cycle/cycle3/program.js -------------------------------------------------------------------------------- /tdd/tdd-cycle/cycle3/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | 3 | test('Practice the concept of test-driven development', nest => { 4 | 5 | nest.test('Unit test the average function', assert => { 6 | const tdd = require('./program'); 7 | assert.ok(tdd.average, "Average Function Doesn't Exist"); 8 | assert.end(); 9 | }); 10 | 11 | }); -------------------------------------------------------------------------------- /tdd/tdd-cycle/cyclefinal/program.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/software-testing-workshop/a951049f637879ea1e5e76a4588a5b1bac9eb926/tdd/tdd-cycle/cyclefinal/program.js -------------------------------------------------------------------------------- /tdd/tdd-cycle/cyclefinal/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | 3 | test('Practice the concept of test-driven development', nest => { 4 | 5 | nest.test('Unit test the average function', assert => { 6 | const tdd = require('./program'); 7 | assert.ok(tdd.average, "Average Function Doesn't Exist"); 8 | assert.end(); 9 | }); 10 | 11 | }); -------------------------------------------------------------------------------- /test-fixtures/program.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tap').test; 4 | const request = require('supertest'); 5 | 6 | const app = require('../app'); 7 | const statusCodes = require('../constants/constants')["statusCodes"]; 8 | const db = require('../models/crudOperations'); 9 | 10 | test('setup', t => { 11 | t.plan(1); 12 | return db.insertDocument({ 13 | dbName: 'softwaretesting', 14 | name: 'movies', 15 | body: { 16 | movies: ['Rocky', 'Rambo', 'Blood Sport'] 17 | } 18 | }) 19 | .then(res => { 20 | t.deepEquals(res.movies, ['Rocky', 'Rambo', 'Blood Sport']); 21 | t.end(); 22 | }); 23 | }); 24 | 25 | test('retrieve movie list from endpoints', t => { 26 | // Add rest endpoint call here 27 | }); 28 | 29 | test('teardown', t => { 30 | t.plan(0); 31 | return db.deleteDocument({ 32 | dbName: 'softwaretesting', 33 | name: 'movies' 34 | }) 35 | .then(res => { 36 | t.end(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test-fixtures/solution/program.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tap').test; 4 | const request = require('supertest'); 5 | 6 | const app = require('../../app'); 7 | const statusCodes = require('../../constants/constants')["statusCodes"]; 8 | const db = require('../../models/crudOperations'); 9 | 10 | test('setup', t => { 11 | t.plan(1); 12 | return db.insertDocument({ 13 | dbName: 'softwaretesting', 14 | name: 'movies', 15 | body: { 16 | movies: ['Rocky', 'Rambo', 'Blood Sport'] 17 | } 18 | }) 19 | .then(res => { 20 | t.deepEquals(res.movies, ['Rocky', 'Rambo', 'Blood Sport']); 21 | t.end(); 22 | }); 23 | }); 24 | 25 | test('retrieve movie list from endpoints', t => { 26 | t.plan(2); 27 | const ok = statusCodes["ok"]; 28 | const expected = ['Rocky', 'Rambo', 'Blood Sport']; 29 | request(app) 30 | .get('/api/v1/couch/retrieveDocument/movies') 31 | .set('Accept', 'application/json') 32 | .expect(res => { 33 | t.equal(res.status, ok); 34 | const movies = res.body.movies 35 | t.same( 36 | movies, 37 | expected, 38 | `Should return ${expected}` 39 | ); 40 | }) 41 | .end((err, res) => { 42 | t.end(); 43 | }); 44 | }); 45 | 46 | test('teardown', t => { 47 | t.plan(0); 48 | return db.deleteDocument({ 49 | dbName: 'softwaretesting', 50 | name: 'movies' 51 | }) 52 | .then(res => { 53 | t.end(); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /unit-test/exercises/concatAll/concatAll.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Function merges multiple arrays into one flat array. 3 | */ 4 | Array.prototype.concatAll = function() { 5 | let results = []; 6 | for (let i = 0; i < this.length; i++) { 7 | results.push.apply(results, this[i]); 8 | } 9 | return results; 10 | }; -------------------------------------------------------------------------------- /unit-test/exercises/concatMap/concatMap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Function does the same behavior as map and concatAll in action. 3 | * @param fn => fn is a callback. 4 | */ 5 | Array.prototype.concatMap = function(fn) { 6 | return this 7 | .map((item, index, arr) => fn(index)) 8 | .concatAll(); 9 | }; -------------------------------------------------------------------------------- /unit-test/exercises/filter/filter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Function takes a callback 3 | * @param fn => function 4 | */ 5 | Array.prototype.customFilter = function(fn) { 6 | let results = []; 7 | for (let i = 0; i < this.length; i++) { 8 | let itemExists = fn(this[i]); 9 | if (itemExists) { 10 | results.push(this[i]); 11 | } 12 | } 13 | return results; 14 | }; -------------------------------------------------------------------------------- /unit-test/exercises/map/map.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Function takes a callback. 3 | * @param fn => function 4 | */ 5 | Array.prototype.customMap = function(fn) { 6 | let results = []; 7 | for (let i = 0; i < this.length; i++) { 8 | results.push(fn(this[i])); 9 | } 10 | return results; 11 | }; -------------------------------------------------------------------------------- /unit-test/instructions.md: -------------------------------------------------------------------------------- 1 | ## Unit Tests 2 | 3 | To view lecture notes for this course, please consult the 4 | [github-pages](https://jbelmont.github.io/software-testing). 5 | 6 | [Rediscovery of TDD](https://www.quora.com/Why-does-Kent-Beck-refer-to-the-rediscovery-of-test-driven-development) 7 | 8 | Open program.test.js and go to each TODO block. 9 | 10 | ### 1. Unit Test the Map Function: 11 | 12 | ```js 13 | nest.test('Unit test the map function', assert => { 14 | assert.equal(actual, expected, 15 | `should render default message`); 16 | assert.end(); 17 | }); 18 | ``` 19 | 20 | #### For a typical unit test I usually create 2 variables one named actual and another named expect 21 | 22 | *For `assert.equal(actual, expected, 'My message here')` if actual and expected are equal then the unit test will pass.* 23 | 24 | The map function behaves in the following manner 25 | ```js 26 | [1,2,3,4,5].map(function(number) { 27 | return { 28 | value: number 29 | }; 30 | }); 31 | ``` 32 | 33 | This will return the following structure 34 | ```json 35 | [ 36 | { value: 1 }, 37 | { value: 2 }, 38 | ... 39 | ] 40 | ``` 41 | 42 | Add variables actual and expected to this first unit test. 43 | The equal method expects to get single property/value in order to pass 1 === 1 or "Mike" === "Mike" 44 | The deepEqual method does a deep property check like this [1,2,3] === [1,2,3] 45 | 46 | ### 2. Unit Test the Filter Function. 47 | 48 | The filter function behaves in the following manner 49 | ```js 50 | [1,2,3,4,5].filter(function(number) { 51 | return number > 3; 52 | }); 53 | ``` 54 | 55 | This will return the following structure 56 | `[4, 5]` 57 | 58 | *Either choose `assert.equal` or `assert.deepEqual` but remember deepEqual does a deep check with arrays but equal checks properties.* 59 | 60 | ### 3. Unit Test the concatAll Function. 61 | 62 | The concatAll function behaves in the following manner 63 | ```js 64 | [ 65 | [1,2,3,4,5], 66 | [6,7,8,9,10] 67 | ].concatAll(); 68 | ``` 69 | 70 | This will return the following structure 71 | `[1,2,3,4,5,6,7,8,9,10]` 72 | 73 | ##### Write a Unit Test using the same format as previous 2 exercises. 74 | 75 | ```js 76 | nest.test('I am some text', assert => { 77 | const actual = ...; 78 | const expected = ...; 79 | assert.equal( 80 | actual, 81 | expected, 82 | 'I should another text' 83 | ); 84 | assert.end(); 85 | }); 86 | ``` 87 | 88 | ### 4. Unit Test the concatMap Function. 89 | 90 | The concatMap function behaves in the following manner 91 | ```js 92 | const numStrings = [ ["One", "Two", "Three"], ["Four", "Five", "Six"] ]; 93 | [1, 2, 3, 4, 5].concatMap(function(num) { 94 | return numStrings[num]; 95 | }); 96 | ``` 97 | 98 | This will return the following structure 99 | `["One", "Two", "Three", "Four", "Five", "Six"]` 100 | -------------------------------------------------------------------------------- /unit-test/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | 3 | // Load Array.prototype.customMap function 4 | require('./exercises/map/map'); 5 | // Load Array.prototype.customFilter function 6 | require('./exercises/filter/filter'); 7 | // Load Array.prototype.concatAll function. 8 | require('./exercises/concatAll/concatAll'); 9 | // Load Array.prototype.concatMap function. 10 | require('./exercises/concatMap/concatMap'); 11 | 12 | test('Practice the concept of unit testing with simple tape library', function(nest) { 13 | 14 | // TODO: Make unit test pass with actual and expected assertion. 15 | nest.test('Unit test the map function', assert => { 16 | const numbers = [1, 2, 3, 4, 5]; 17 | const actual = numbers.customMap(function(elem) { 18 | return elem + 1; 19 | }); 20 | const expected = [2, 3, 4, 5, 6]; 21 | assert.deepEqual(actual, expected, 22 | `should render default message`); 23 | assert.end(); 24 | }); 25 | 26 | // TODO: Unit test filter function 27 | nest.test('Unit test the filter function', assert => { 28 | assert.end(); 29 | }); 30 | 31 | // TODO: Unit test concatAll function 32 | 33 | // TODO: Unit test concatMap function 34 | }); 35 | -------------------------------------------------------------------------------- /unit-test/solution/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | 3 | // Load Array.prototype.customMap function 4 | require('../exercises/map/map'); 5 | // Load Array.prototype.customFilter function 6 | require('../exercises/filter/filter'); 7 | // Load Array.prototype.concatAll function. 8 | require('../exercises/concatAll/concatAll'); 9 | // Load Array.prototype.concatMap function. 10 | require('../exercises/concatMap/concatMap'); 11 | 12 | test('Practice the concept of unit testing with simple tape library', nest => { 13 | 14 | nest.test('Unit test the Array.prototype.customMap function', assert => { 15 | const actual = [1,2,3,4,5]; 16 | const expected = actual.customMap(numbers => numbers); 17 | assert.deepEqual(actual, expected, `Should return [1,2,3,4,5]`); 18 | assert.end(); 19 | }); 20 | 21 | nest.test('Unit test the Array.prototype.customFilter function', assert => { 22 | const actual = [1,2,3,4,5]; 23 | const expected = actual.customFilter(num => num > 3); 24 | assert.deepEqual([4,5], expected, `Should return [4,5]`); 25 | assert.end(); 26 | }); 27 | 28 | nest.test('Unit test the Array.prototype.concatAll function', assert => { 29 | const actual = [ [1,2,3,4,5], [6,7,8,9,10] ]; 30 | const expected = [1,2,3,4,5,6,7,8,9,10]; 31 | assert.deepEqual( 32 | actual.concatAll(), 33 | expected, 34 | 'Should flatten and return [1,2,3,4,5,6,7,8,9,10]' 35 | ); 36 | assert.end(); 37 | }); 38 | 39 | nest.test('Unit test the Array.prototype.concatMap function', assert => { 40 | const actual = [ 41 | ["my","name","is"], 42 | ["John","J","Rambo"], 43 | ["and","I","rock"] 44 | ]; 45 | const expected = ["my","name","is", "John","J","Rambo", "and","I","rock" ]; 46 | 47 | assert.deepEqual( 48 | [1,2,3,4,5].concatMap((item,idx,arr) => actual[item]), 49 | expected, 50 | 'Should return flatten list ["my","name","is", "John","J","Rambo", "and","I","rock" ]' 51 | ); 52 | assert.end(); 53 | }); 54 | 55 | }); -------------------------------------------------------------------------------- /users/users.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | const router = express.Router(); 5 | const path = require('path'); 6 | const winston = require('winston'); 7 | 8 | const db = require('../models/crudOperations'); 9 | 10 | router.get('/badMofos', (req, res, next) => { 11 | db.retrieveDocument({ dbName: 'softwaretesting', name: 'badMofos' }) 12 | .then(users => { 13 | res.send(users); 14 | }) 15 | .catch(err => { 16 | res.send(err); 17 | }); 18 | }); 19 | 20 | router.post('/addUser', (req, res, next) => { 21 | const { 22 | user 23 | } = req.body; 24 | db.insertDocument({ dbName: 'softwaretesting', name: 'users' }) 25 | .then(users => { 26 | res.send(users); 27 | }) 28 | .catch(err => { 29 | res.send(err); 30 | }); 31 | }); 32 | 33 | module.exports = router; 34 | -------------------------------------------------------------------------------- /views/error.hbs: -------------------------------------------------------------------------------- 1 |
{{error}}
-------------------------------------------------------------------------------- /views/index.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{title}} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* jshint browserify: true */ 2 | 3 | const webpack = require('webpack'); 4 | 5 | module.exports = { 6 | entry: { 7 | App: './static/js/components/App.jsx' 8 | }, 9 | output: { 10 | path: __dirname + '/static/build', 11 | filename: '[name].js' 12 | }, 13 | devtool: 'source-map', 14 | module: { 15 | loaders: [{ 16 | test: /\.jsx?$/, 17 | loader: 'babel', 18 | query: { 19 | presets:['es2015','react', 'stage-0'] 20 | } 21 | },{ 22 | test: /\.scss$/, 23 | loader: 'style!css!autoprefixer!sass?sourceMap' 24 | }] 25 | }, 26 | resolve: { 27 | extensions: ['', '.js', '.jsx'] 28 | }, 29 | externals: { 30 | react: 'React' 31 | }, 32 | plugins: [ 33 | new webpack.DefinePlugin({ 34 | 'process.env': { 35 | 'NODE_ENV': JSON.stringify('development') 36 | } 37 | }), 38 | new webpack.HotModuleReplacementPlugin() 39 | ] 40 | }; 41 | --------------------------------------------------------------------------------