26
loading...
This website collects cookies to deliver better user experience
Extra code: How many times did you end with code that is not used? Maybe we added some extra loops that are not needed or some function to do something and then realize that we are not using it. When we code our modules before any test, we don't actually know what we are going to need or if our algorithm is going to support any kind of input (that could lead to those extra loops). More code means more stuff to maintain which also means, more money.
Bad API design: Maybe we need to create a new service to do something, and then we start writing functions to do the work and we put some of them public to define the service's API. Good, that is the idea isn't it? Some time after we get complaints about our really poor API that well, it is not as intuitive as we expected. In this category also goes those API functions that are not really needed (which is also extra code).
Refactor: What happens when we want to refactor our code? We are in big trouble. Even when we decide to not break the API, maybe that internal change is not working properly on some edge cases where it worked in the past. That will break the application for some people and they won't be happy at all (and those kind of bugs are normally a pain to debug).
Will it work: That is the end goal and probably the biggest time waster of anything you have to do in your applicaton. Something as simple as a calendar, involves some maths and some magic numbers to make it work. We really need to be sure it works. How? We open a certain date, we manually check with our OS calendar to see if it matches. We repeat that for some random dates (old ones, future ones). Then we change something in our service and well, we need to check the dates again to assert that nothing is broken. Repeat that 20 times for a normal service development.
Jest
. Jest is a testing framework that can be used for any Javascript / Typescript project.5
and 3
and get 8
. Let's test that.src/calculator.spec.ts
describe('Calculator', () => {
it('should be able to sum 5 and 3 to return 8', () => {
// Arrange
const calc = new Calculator();
// Act
const result = calc.sum(5, 3);
// Assert
expect(result).toBe(8);
});
});
Tests
tab at codesandbox
, let's talk about this piece of code.Describe
a calculator. It
should be able to run 5 and 3 to return 8. Create a calculator object, call a method and expect
the result to be
8.".describe
functions. They are used to group our tests. The actual tests are functions called it
where we actually code our tests.it
functions, we follow a pattern called AAA (Arrange, Act, Assert). With those 3 steps, we successfully write a test.Calculator
object, then Acting by calling it's sum
method and Asserting by checking its result with our expected result.Calculator
class.sum
method before we created the class.src/calculator.ts
export class Calculator {
sum(num1: number, num2: number): number {
return 8;
}
}
src/Calculator.spec.ts
:import { Calculator } from './calculator';
describe('Calculator', () => {
...
});
src/calculator.spec.ts
it('should be able to sum a number with 0', () => {
const calc = new Calculator();
const result = calc.sum(7, 0);
expect(result).toBe(7);
});
src/calculator.ts
export class Calculator {
sum(num1: number, num2: number): number {
return num1 + num2;
}
}
import { Calculator } from './calculator';
describe('Calculator', () => {
it('should be able to sum 5 and 3 to return 8', () => {
// Arrange
const calc = new Calculator();
// Act
const result = calc.sum(5, 3);
// Assert
expect(result).toBe(8);
});
it('should be able to sum a number with 0', () => {
const calc = new Calculator();
const result = calc.sum(7, 0);
expect(result).toBe(7);
});
});
it
in our spec file is completely independent of the others. They run independently and you should never ever rely on the way they are ordered to "start something in one one them" and "assert in the other". In fact, Jest may run the it
in a random order to avoid dependency between them.Arrange
part in those two tests, and if we have 20 of them, we are going to repeat it 20 times. We can do better.beforeEach
that runs before each it
function. There we can setup whatever we need for each test. Let's Arrange our code there so we have access to calc
in each test.src/calculator.spec.ts
:import { Calculator } from './calculator';
describe('Calculator', () => {
let calc: Calculator;
beforeEach(() => {
// Arrange
calc = new Calculator();
});
it('should be able to sum 5 and 3 to return 8', () => {
// Act
const result = calc.sum(5, 3);
// Assert
expect(result).toBe(8);
});
it('should be able to sum a number with 0', () => {
const result = calc.sum(7, 0);
expect(result).toBe(7);
});
});
it('should be able to sum a negative number for a positive result', () => {
const result = calc.sum(7, -3);
expect(result).toBe(4);
});
it('should be able to rum a negatrive number for a negative result', () => {
expect(calc.sum(-20, 7)).toBe(-13);
});
division
, but before we do that, we could group or sum
test in their own describe
like this:src/calculator.spec.ts
:import { Calculator } from './calculator';
describe('Calculator', () => {
let calc: Calculator;
beforeEach(() => {
// Arrange
calc = new Calculator();
});
describe('#sum', () => {
it('should be able to sum 5 and 3 to return 8', () => {
// Act
const result = calc.sum(5, 3);
// Assert
expect(result).toBe(8);
});
it('should be able to sum a number with 0', () => {
const result = calc.sum(7, 0);
expect(result).toBe(7);
});
it('should be able to sum a negative number for a positive result', () => {
const result = calc.sum(7, -3);
expect(result).toBe(4);
});
it('should be able to rum a negatrive number for a negative result', () => {
expect(calc.sum(-20, 7)).toBe(-13);
});
});
});
describe
as we need. Also notice the #
at #sum
. It is a convention that says that we are testing a method.describe
for a division with a simple test:src/calculator.spec.ts
:it('should be able to rum a negatrive number for a negative result', () => {
expect(calc.sum(-20, 7)).toBe(-13);
});
});
describe('#division', () => {
it('should be able to do an exact division', () => {
const result = calc.division(20, 2);
expect(result).toBe(10);
});
});
src/calculator.ts
:export class Calculator {
sum(num1: number, num2: number): number {
return num1 + num2;
}
division(num1: number, num2: number): number {
return num1 / num2;
}
}
division
method.Calculator
to deal with decimals, because who likes decimal anyway?src/calculator.spec.ts
:it('returns a rounded result for a non exact division', () => {
expect(calc.division(20, 3)).toBe(7)
});
src/calculator.spec.ts
:export class Calculator {
sum(num1: number, num2: number): number {
return num1 + num2;
}
division(num1: number, num2: number): number {
return Math.round(num1 / num2);
}
}
src/calculator.spec.ts
:it('throws an exception if we divide by 0', () => {
expect(() =>
calc.division(5, 0)
).toThrow('Division by 0 not allowed.');
});
expect
, we are passing a function. The idea is something like "We expect that when running this function, an exception will be thrown". Since division
won't be able to return anything if it throws an exception, we cannot test the result
as we previously did.spec/calculator.ts
:export class Calculator {
sum(num1: number, num2: number): number {
return num1 + num2;
}
division(num1: number, num2: number): number {
return Math.round(num1 / num2);
}
}
src/calculator.spec.ts
:xit('throws an exception if we divide by 0', () => {
expect(() =>
calc.division(5, 0)
).toThrow('Division by 0 not allowed.');
});
xit
. We use this as a way to "ignore" a test. We can always comment out the code, but that way we may forget that we had a test to fix. With xit
we can see that it exist but that it was skipped.NOTE: codesandbox doesn't manage this xit
very well, but at least it says that there are no failing tests
export class Calculator {
sum(num1: number, num2: number): number {
return num1 + num2;
}
division(dividend: number, divisor: number): number {
return Math.round(dividend / divisor);
}
}
NOTE: As mentioned, codesandbox doesn't manage this well and you may see a red X saying failed but all correct in the summary, that is fine.
xit
for it
again:src/calculator.spec.ts
:it('throws an exception if we divide by 0', () => {
expect(() =>
calc.division(5, 0)
).toThrow('Division by 0 not allowed.');
});
export class Calculator {
sum(num1: number, num2: number): number {
return num1 + num2;
}
division(dividend: number, divisor: number): number {
if (divisor === 0) {
throw new Error('Division by 0 not allowed.');
}
return Math.round(dividend / divisor);
}
}