Implementing an Array filter function on the Array prototype using TDD (Test-Driven Development)
Objective: Learn how to implement a custom Array filter function on the Array prototype by following the Test-Driven Development approach.
Prerequisites:
- Basic understanding of JavaScript
- Familiarity with Array methods, particularly Array.prototype.filter
- Knowledge of TDD concepts and a testing library (we'll use Jest in this exercise)
Steps:
- Initialize the project and set up Jest testing library
Create a new folder for the project and navigate to it in your terminal. Initialize the project by running:
npm init -y
Next, install Jest as a development dependency:
npm install --save-dev jest
Modify your package.json
to include a test script:
{
"scripts": {
"test": "jest"
},
...
}
- Create a test file
Create a new file called arrayFilter.test.js
in the project folder. In this file, we'll write the test cases for our custom filter function one by one, following the proper TDD process.
- Implement the customFilter function on the Array prototype
Create a new file named arrayFilter.js
in the project folder. For now, leave the file empty:
// arrayFilter.js
- Write the first test case, implement the minimum required behavior, and run the tests
Test 1: filters an array based on a provided callback function
Add the first test case to arrayFilter.test.js
:
const { customFilter } = require('./arrayFilter');
describe('customFilter', () => {
test('filters an array based on a provided callback function', () => {
const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.customFilter(number => number % 2 === 0);
expect(evenNumbers).toEqual([2, 4]);
});
});
Now, write the minimum required implementation in arrayFilter.js
to make the first test pass:
Array.prototype.customFilter = function(callback) {
const filteredArray = [];
for (let i = 0; i < this.length; i++) {
if (callback(this[i], i, this)) {
filteredArray.push(this[i]);
}
}
return filteredArray;
};
module.exports = {
customFilter: Array.prototype.customFilter
};
Run the tests using the test
script:
npm test
The first test should pass. Now, we'll follow the TDD cycle of "Red, Green, Refactor" for each additional test case.
- Write the next test case, update the implementation, and refactor (if needed)
For each remaining test case, write the test, run the tests, and make any necessary changes to the implementation until the test passes. If needed, refactor the code for better design, readability, or performance. Repeat this process for all test cases.
Test 2: does not modify the original array
Add the second test case to arrayFilter.test.js
:
test('does not modify the original array', () => {
const originalArray = [1, 2, 3];
const newArray = originalArray.customFilter(number => number > 1);
expect(originalArray).toEqual([1, 2, 3]);
});
Run the tests:
npm test
The second test should already pass with the current implementation. No additional changes are required for this test.
Test 3: returns an empty array when no elements pass the test
Add the third test case to arrayFilter.test.js
:
test('returns an empty array when no elements pass the test', () => {
const numbers = [1, 2, 3];
const result = numbers.customFilter(number => number > 10);
expect(result).toEqual([]);
});
Run the tests:
npm test
The third test should also pass with the current implementation. No additional changes are required for this test.
Test 4: returns an empty array when called on an empty array
- Add the fourth test case to
arrayFilter.test.js
:
test('returns an empty array when called on an empty array', () => {
const emptyArray = [];
const result = emptyArray.customFilter(number => number > 0);
expect(result).toEqual([]);
});
Run the tests:
npm test
The fourth test should pass with the current implementation. No additional changes are required for this test.
Test 5: throws an error if the argument is not a function
Add the fifth test case to arrayFilter.test.js
:
test('throws an error if the argument is not a function', () => {
const numbers = [1, 2, 3];
expect(() => {
numbers.customFilter('not a function');
}).toThrow(TypeError);
});
Run the tests:
npm test
The fifth test will fail with the current implementation. Now, update the arrayFilter.js
file to include a check for the callback type:
Array.prototype.customFilter = function(callback) {
if (typeof callback !== 'function') {
throw new TypeError('Argument must be a function');
}
const filteredArray = [];
for (let i = 0; i < this.length; i++) {
if (callback(this[i], i, this)) {
filteredArray.push(this[i]);
}
}
return filteredArray;
};
module.exports = {
customFilter: Array.prototype.customFilter
};
Run the tests again:
npm test
All tests should now pass, and you've successfully followed the TDD approach for implementing the custom filter function on the Array prototype.