In the previous article, we looked at theoretical aspects. It's time to start practicing.

Let's make a simple implementation of the stack in JavaScript with the help of development through testing.
')
A stack is a data structure organized on the principle of LIFO: Last In, First Out. There are three main operations on the stack:
push : add item
pop : delete item
peek : add head element
Create a class and call it Stack. To complicate the task, suppose the stack has a fixed capacity. Here are the properties and functions of implementing our stack:
items : stack items. We will use an array to implement the stack.
capacity : the capacity of the stack.
isEmpty () : returns true if the stack is empty, otherwise false.
isFull () : returns true if the stack reaches its maximum capacity, i.e. when you cannot add another item. Otherwise, returns false.
push (element) : adds an element. Returns Full if the stack is full.
pop : deletes the item. Returns Empty if stack is empty.
peek () : adds a head element.
We are going to create two
stack.js and
stack.spec.js files . I used the
.spec.js extension because I was used to it, but you can use .test.js or give it a different name and move it to
__tests__ .
As we practice in development through testing, we will write a failing test.
First, check the constructor. To test a file, you need to import a stack file:
const Stack = require('./stack')
For those who are wondering why I didn’t use
import here, the latest stable version of Node.js does not support this feature today. I could add Babel, but I do not want to overload you.
When you are testing a class or function, run the test and describe which file or class you are testing. Here we are talking about the stack:
describe('Stack', () => { })
Then we need to check that when the stack is initialized, an empty array is created, and we set the correct capacity. So, we write the following test:
it('Should constructs the stack with a given capacity', () => { let stack = new Stack(3) expect(stack.items).toEqual([]) expect(stack.capacity).toBe(3) })
Notice that we use
toEqual and do not use
toBe for
stack.items , because they do not refer to the same array.
Now run
yarn test stack.spec.js . We run
Jest in a specific file, because we don’t want other tests to be corrupted. Here is the result:
Stack is not a constructor . Of course. We still have not created our stack and have not done a constructor.
In
stack.js, create your class constructor and export the class:
class Stack { constructor() { } } module.exports = Stack
Run the test again:

Since we did not set the elements in the constructor,
Jest expected the elements in the array to be [], but they are not defined. Then you must initialize the elements:
constructor() { this.items = [] }
If you run the test again, you will get the same error for
capacity , so you will also need to set the capacity:
constructor(capacity) { this.items = [] this.capacity = capacity }
Run the test:

Yes!
Test passed . That's what TDD is. Hopefully now testing will make more sense to you! Continue?
isEmpty
To test
isEmpty , we are going to initialize an empty stack, test if isEmpty returns true, add an item and check it again.
it('Should have an isEmpty function that returns true if the stack is empty and false otherwise', () => { let stack = new Stack(3) expect(stack.isEmpty()).toBe(true) stack.items.push(2) expect(stack.isEmpty()).toBe(false) })
When you run a test, you should get the following error:
TypeError: stack.isEmpty is not a function
To solve this problem, we need to create
isEmpty inside the
Stack class:
isEmpty () { }
If you run the test, you should get another error:
Expected: true Received: undefined
Nothing is added to
isEmpty .
Stack is empty if there are no items in it:
isEmpty () { return this.items.length === 0 }
isFull
This is the same as
isEmpty , since this exercise tests this function using TDD. You will find a solution at the very end of the article.
Push
Here we need to test
three things:
- A new element must be added to the top of the stack;
- push returns "Full" if the stack is full;
- The item that was recently added must be returned.
Create another block using
describe for
push . We put this block inside the main one.
describe('Stack.push', () => { })
Add item
Create a new stack and add an item. The last item in the
items array should be the item you just added.
describe('Stack.push', () => { it('Should add a new element on top of the stack', () => { let stack = new Stack(3) stack.push(2) expect(stack.items[stack.items.length - 1]).toBe(2) }) })
If you run the test, you will see that
push is not defined and this is normal.
push will need a parameter to add something to the stack:
push (element) { this.items.push(element) }
The test is passed again. Did you notice something? We keep a copy of this line:
let stack = new Stack(3)
This is very annoying. Fortunately, we have a
beforeEach method that allows you to perform some customization before each test run. Why not take advantage of this?
let stack beforeEach(() => { stack = new Stack(3) })
Important:
The stack must be declared before
beforeEach . In fact, if you define it in the
beforeEach method, the stack variable will not be defined in all tests, because it is not in the right area.
More important: we also need to create the
afterEach method. The stack instance will now be used for all tests. This can cause some difficulties. Immediately after
beforeEach, add this method:
afterEach(() => { stack.items = [] })
Now you can remove the stack initialization in all tests. Here is the complete test file:
const Stack = require('./stack') describe('Stack', () => { let stack beforeEach(() => { stack = new Stack(3) }) afterEach(() => { stack.items = [] }) it('Should constructs the stack with a given capacity', () => { expect(stack.items).toEqual([]) expect(stack.capacity).toBe(3) }) it('Should have an isEmpty function that returns true if the stack is empty and false otherwise', () => { stack.items.push(2) expect(stack.isEmpty()).toBe(false) }) describe('Stack.push', () => { it('Should add a new element on top of the stack', () => { stack.push(2) expect(stack.items[stack.items.length - 1]).toBe(2) }) }) })
Return Value Testing
There is a test:
it('Should return the new element pushed at the top of the stack', () => { let elementPushed = stack.push(2) expect(elementPushed).toBe(2) })
When you run the test, you will receive:
Expected: 2 Received: undefined
Nothing returns inside the
push ! We need to fix this:
push (element) { this.items.push(element) return element }
Return full if stack is full
In this test, we need to first fill the stack, add an element, make sure that nothing extra has been added to the stack and that the return value is
Full .
it('Should return full if one tries to push at the top of the stack while it is full', () => { stack.items = [1, 2, 3] let element = stack.push(4) expect(stack.items[stack.items.length - 1]).toBe(3) expect(element).toBe('Full') })
You will see this error when run the test:
Expected: 3 Received: 4
So the item is added. This is what we wanted. First you need to check if the stack is full before adding something:
push (element) { if (this.isFull()) { return 'Full' } this.items.push(element) return element }
Test passed.Exercise: pop and peekIt's time to practice. Test and implement
pop and
peek .
Tips:
- pop is very similar to push
- peek also looks like a push
- So far, we have not refactored the code, because it was not necessary. In these functions, there may be a way to refactor your code after writing tests and passing them. Do not worry, changing the code, tests for this and need.
Do not look at the solution below without trying to make it yourself. The only way to progress is to try, experiment, and practice.
Decision
Well, how is the exercise? Did you do it? If not, do not be discouraged. Testing takes time and effort.
class Stack { constructor (capacity) { this.items = [] this.capacity = capacity } isEmpty () { return this.items.length === 0 } isFull () { return this.items.length === this.capacity } push (element) { if (this.isFull()) { return 'Full' } this.items.push(element) return element } pop () { return this.isEmpty() ? 'Empty' : this.items.pop() } peek () { return this.isEmpty() ? 'Empty' : this.items[this.items.length - 1] } } module.exports = Stack const Stack = require('./stack') describe('Stack', () => { let stack beforeEach(() => { stack = new Stack(3) }) afterEach(() => { stack.items = [] }) it('Should construct the stack with a given capacity', () => { expect(stack.items).toEqual([]) expect(stack.capacity).toBe(3) }) it('Should have an isEmpty function that returns true if the stack is empty and false otherwise', () => { expect(stack.isEmpty()).toBe(true) stack.items.push(2) expect(stack.isEmpty()).toBe(false) }) it('Should have an isFull function that returns true if the stack is full and false otherwise', () => { expect(stack.isFull()).toBe(false) stack.items = [4, 5, 6] expect(stack.isFull()).toBe(true) }) describe('Push', () => { it('Should add a new element on top of the stack', () => { stack.push(2) expect(stack.items[stack.items.length - 1]).toBe(2) }) it('Should return the new element pushed at the top of the stack', () => { let elementPushed = stack.push(2) expect(elementPushed).toBe(2) }) it('Should return full if one tries to push at the top of the stack while it is full', () => { stack.items = [1, 2, 3] let element = stack.push(4) expect(stack.items[stack.items.length - 1]).toBe(3) expect(element).toBe('Full') }) }) describe('Pop', () => { it('Should removes the last element at the top of a stack', () => { stack.items = [1, 2, 3] stack.pop() expect(stack.items).toEqual([1, 2]) }) it('Should returns the element that have been just removed', () => { stack.items = [1, 2, 3] let element = stack.pop() expect(element).toBe(3) }) it('Should return Empty if one tries to pop an empty stack', () => {
If you look at the files, you will see that I used the triple condition in pop and peek. This is what I refactor. The old implementation looked like this:
if (this.isEmpty()) { return 'Empty' } return this.items.pop()
Developing through testing allows us to refactor the code after the tests were written, I found a shorter implementation, without worrying about the behavior of my tests.
Run the test again:

Tests improve the quality of the code. I hope you now understand all the benefits of testing and will use TDD more often.
We managed to convince you that testing is essential and improves the quality of the code? Rather, read the 2 part of the article about the development through testing. And who did not read the first,
follow the link .