Test Driven Development (TDD)
This is a follow up assignment/activity after completing the unit testing activity in the node-sample-project.
Students should read this article on unit testing and test-driven development before doing this activity
When introducing the assignment, make sure to show how to run just one test:
npx jest ./tests/artist.model.test.js
Also, how to run all tests in the tests folder:
npx jest ./tests
Also, how to add a script to the package.json file:
"scripts": {
"test": "npx jest ./tests"
},
Now you can enter this command to run all your tests: npm run test
Test-driven development is a way of writing code for a component only after all the tests have been created.
Create a file named artist.model.js inside the modules folder and put the following code into it.
You need to understand what a model object is:
- Usually represents a row from a database table
- Often has logic (a function) to validate it's state.
class Artist {
constructor({id,name, birthDate}){
// if the id is undefined, it should default to 0
// if the name is undefined, it should default to an empty string
// if the birthDate is undefined, it should default to NaN
}
validate(){
let isValid = true;
const errorMessages = {};
// VALIDATE THE id PROPERTY
// It must be a number greater than 0
// If it is not a number, then do the following:
// 1. set isValid to false
// 2. set errorMessages.id to "The artist id must be a number"
// If it is not greater than 0, then do the following:
// 1. set isValid to false
// 2. set errorMessages.id to "The artist id must be 0 or greater"
// VALIDATE THE name PROPERTY
// It must be a non-empty string that is 120 characters or less
// If it is not a string, then do the following:
// 1. set isValid to false
// 2. set errorMessages.name to "The artist name must be a string"
// If it is an empty string, then do the following:
// 1. set isValid to false
// 2. set set errorMessages.name to "The artist name is required"
// If it is more than 120 characters, then do the following:
// 1. set isValid to false
// 2. set set errorMessages.name to "The artist name must be 120 characters or less"
// VALIDATE THE birthDate PROPERTY
// It must be a string that represents a valid date which is later than 1900 but before the current date
// If it can not be parsed into a valid Date object:
// 1. set isValid to false
// 2. set errorMessages.birthDate to "The artist birthday is not a valid date"
// If it is earlier than 1900-1-1:
// 1. set isValid to false
// 2. set errorMessages.birthDate to "The artist birthday cannot be before 1900"
// If it is later than the current Date
// 1. set isValid to false
// 2. set errorMessages.birthDate to "The artist birthday cannot be in the future"
return [isValid, errorMessages]
}
}
module.exports = Artist;
You need to understand what a model object is
Notes about the contructor:
- The constructor destructures an object param that has an id property and a name proerty
- If the id is not defined, it should default to 0
- If the name is not defined, it should default to an empty string
Notes about validate():
- It returns an array that has 2 elements in it
- The first element should be true or false
- The second element is an object that has information (properties) about why the object is not valid
Review the comments in the validate() function.
Create a file in the tests folder named artist.model.test.js and put this code in it:
const Artist = require("../modules/artist.model");
describe("Artist Model", () => {
describe("Constructor", () => {
it("should set the instance variables properly", ()=>{
const artist = new Artist({id:1, name:"Taylor Swift", birthDate:"2001-01-21"});
expect(artist).toHaveProperty("id", 1);
expect(artist).toHaveProperty("name", "Taylor Swift");
expect(artist).toHaveProperty("birthDate", "2001-01-21")
})
it("id should default to 0 if not included in param", ()=>{
const artist = new Artist({});
expect(artist).toHaveProperty("id", 0);
})
it("name should default to empty string if not included in param", ()=>{
const artist = new Artist({});
expect(artist).toHaveProperty("name", "");
})
it("birthDate should default to NaN if not included in param", ()=>{
const artist = new Artist({});
expect(artist).toHaveProperty("birthDate", NaN);
})
}) // end of constructor tests
describe("validate()", () => {
it("should return proper values if all properties are valid", () => {
const artist = new Artist({id:1, name:"Taylor Swift", birthDate: "2001-01-21"});
const [isValid, errs] = artist.validate();
expect(isValid).toBe(true);
expect(errs).toEqual({});
})
it("should return proper values if the id property is not a number", () => {
let artist = new Artist({id:"1", name:"Taylor Swift", birthDate: "2001-01-21"});// invalid id, must be a number
let [isValid, errs] = artist.validate();
expect(isValid).toBe(false);
expect(errs).toHaveProperty("id", "The artist id must be a number");
})
it("should return proper values if the id property is not greater than 0", () => {
let artist = new Artist({id:-8, name:"Taylor Swift", birthDate: "2001-01-21"}); // invalid id - less than 0
let [isValid, errs] = artist.validate();
expect(isValid).toBe(false);
expect(errs).toHaveProperty("id", "The artist id must be 0 or greater");
})
it("should return proper values if the name property is not a string", () => {
let artist = new Artist({id:1, name:9, birthDate: "2001-01-21"}); // invalid name, not a string
let [isValid, errs] = artist.validate();
expect(isValid).toBe(false);
expect(errs).toHaveProperty("name", "The artist name must be a string");
})
it("should return proper values if the name property is an empty string", () => {
let artist = new Artist({id:1, name:"", birthDate: "2001-01-21"}); // invalid name, empty string
let [isValid, errs] = artist.validate();
expect(isValid).toBe(false);
expect(errs).toHaveProperty("name", "The artist name is required");
})
it("should return proper values if the name property is more than 120 characters", () => {
const someName = "x".repeat(121); // invalid name, more than 120 characters
let artist = new Artist({id:1, name: someName, birthDate: "2001-01-21"});
let [isValid, errs] = artist.validate();
expect(isValid).toBe(false);
expect(errs).toHaveProperty("name", "The artist name must be 120 characters or less");
})
it("should return proper values if the birthDate property is not a valid date", () => {
let artist = new Artist({id:1, name:"Taylor Swift", birthDate: "XXXX"});// invalid birthDate!!
let [isValid, errs] = artist.validate();
expect(isValid).toBe(false);
expect(errs).toHaveProperty("birthDate", "The artist birthday is not a valid date");
})
it("should return proper values if the birthDate property is before 1900", () => {
let artist = new Artist({id:1, name:"Taylor Swift", birthDate: "1899-1-1"});// invalid birthDate - BEFORE 1900
let [isValid, errs] = artist.validate();
expect(isValid).toBe(false);
expect(errs).toHaveProperty("birthDate", "The artist birthday cannot be before 1900");
})
it("should return proper values if the birthDate property is later than the current date", () => {
const currentDate = new Date();
const futureDate = new Date().setFullYear(currentDate.getFullYear() + 1);
let artist = new Artist({id:1, name:"Taylor Swift", birthDate: futureDate});// invalid birthDate - after the current date
let [isValid, errs] = artist.validate();
expect(isValid).toBe(false);
expect(errs).toHaveProperty("birthDate", "The artist birthday cannot be in the future");
})
}) // end of validate() tests
})
Run the tests, most/all of them should be failing. I believe that only one of the tests will be passing (the first validate() test)
We'll get the constructor tests to pass together.
And we'll get started on the validate() tests together.
Your assignment is to get all the tests to pass.