Pure functions and basic tests in Jasmine
Pure functions are the perfect case for unit testing. For a given input, we always expect the same output—there is no internal state involved. Let’s take a look at a few examples and some simple tests that check if the methods work as expected.
Jasmine
Jasmine is a unit test framework for JavaScript. It can run tests in both Node.js or on the browser. It’s used in the Angular framework, and it’s especially popular in projects based on Angular. It’s a solid choice for Vanilla JS projects, or projects based on other frameworks as well.
Happy path testing
Happy path testing is when we test a method with inputs that it’s expected to work normally using. The arguments are valid and within reasonable ranges. Those tests check if the method does its job correctly—the test cases should be straightforward examples of how the method is explained in its documentation.
Pseudocode examples:
expect(add(2, 2)).toBe(4)
,expect(concatenate(“Lorem”, “Ipsum”)).toBe(“LoremIpsum”)
Those tests are meant to automatically catch it anytime the method key behavior is broken.
Methods
Let’s see a few simple methods: simple operations that we might need in some real-world application.
All implementations are greatly simplified—all methods will break in an ugly way if only we provide them with parameters that differ slightly from what is expected. The code is far from being robust.
greet
Method that greets the user with their name and surname:
export function greet(name, surname) {
return `Hello ${name} ${surname}!`;
}
shortDate
shortDate
is a formatting method that takes a date object and returns it formatted as a short string. The code:
export function shortDate(date) {
return date.toISOString().substring(0, 10);
}
ellipsis
ellipsis
takes a long text string and an optional length parameter and then trims the string to fit within the limit:
export function ellipsis(text, length = 50) {
if (text.length > length) {
return text.substring(0, length) + "…";
}
return text;
}
translate
A method that provides translated string values for a key
and lang
pair. It’s a simplified implementation of what could be replaced with more advanced translating libraries.
export function translate(key, lang = "en") {
switch (lang) {
case "en":
switch (key) {
case "hello":
return "Hello!";
}
case "pl":
switch (key) {
case "hello":
return "Cześć!";
}
}
}
applyDiscount
Method to apply a percentage discount to a price. It can feel like overkill with this naive implementation, but later when we start investigating edge cases it will get much more interesting.
export function applyDiscount(price, discountPercentage) {
return price - (price * discountPercentage) / 100;
}
calculatePrice
This one calculates the total price when buying multiple units at a given price. It will also get more complicated after adding interesting edge cases.
export function calculatePrice(unitPrice, quantity) {
return unitPrice * quantity;
}
Complete JS code
The complete JS code, src/main.js
:
export function greet(name, surname) {
return `Hello ${name} ${surname}!`;
}
export function shortDate(date) {
return date.toISOString().substring(0, 10);
}
export function ellipsis(text, length = 50) {
if (text.length > length) {
return text.substring(0, length) + "…";
}
return text;
}
export function translate(key, lang = "en") {
switch (lang) {
case "en":
switch (key) {
case "hello":
return "Hello!";
}
case "pl":
switch (key) {
case "hello":
return "Cześć!";
}
}
}
export function applyDiscount(price, discountPercentage) {
return price - (price * discountPercentage) / 100;
}
export function calculatePrice(unitPrice, quantity) {
return unitPrice * quantity;
}
Adding Jasmine tests
To add Jasmine, let’s start by converting the folder into an npm package:
$ npm init -y
Wrote to …/package.json:
…
Then we can install the Jasmine package:
$ npm install --save-dev jasmine
added 42 packages, and audited 43 packages in 2s
13 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
then we can generate folders and files used by Jasmine:
$ npx jasmine init
(no output)
This command generates the following:
spec/
—a folder where we can put*.spec.js
files with the test, andspec/support/jasmine.json
—a file with the Jasmine config.
Unit tests
For the following unit tests, I’m focusing on the happy path only—I check if the result is as expected for reasonable inputs. The test should be self-explanatory, so let’s take a look at them:
import {
greet,
shortDate,
ellipsis,
translate,
applyDiscount,
calculatePrice,
} from "../src/main.js";
describe("main", () => {
describe("greet", () => {
it("should greet by name and surname", () => {
expect(greet("Lorem", "Ipsum")).toEqual("Hello Lorem Ipsum!");
});
});
describe("shortDate", () => {
it("should format correclty date", () => {
const date = new Date("2023-11-02");
expect(shortDate(date)).toEqual("2023-11-02");
});
});
describe("shortDate", () => {
it("should shorten long text at 50 chars", () => {
expect(
ellipsis(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque a faucibus massa."
)
).toEqual("Lorem ipsum dolor sit amet, consectetur adipiscing…");
});
it("should leave short text unchanged", () => {
expect(ellipsis("Lorem ipsum sin dolor")).toEqual(
"Lorem ipsum sin dolor"
);
});
it("should shorten to custom length", () => {
expect(ellipsis("Lorem ipsum sin dolor", 10)).toEqual("Lorem ipsu…");
});
});
describe("translate", () => {
it("should translate to supported langauges", () => {
expect(translate("hello", "en")).toEqual("Hello!");
expect(translate("hello", "pl")).toEqual("Cześć!");
});
});
describe("applyDiscount", () => {
it("should lower the price accordingly", () => {
expect(applyDiscount(120, 25)).toEqual(90);
expect(applyDiscount(8, 50)).toEqual(4);
});
});
describe("calculatePrice", () => {
it("should find a price of many products", () => {
expect(calculatePrice(4, 3)).toEqual(12);
expect(calculatePrice(9, 0.5)).toEqual(4.5);
});
});
});
(file spec/main.spec.js
)
Running tests
To run the tests, we can add the following script to package.json
:
..
"scripts": {
"test": "jasmine"
},
…
With this in place, npm run test
runs our tests:
$ npm run test
> testing-example@1.0.0 test
> jasmine
Randomized with seed 76873
Started
........
8 specs, 0 failures
Finished in 0.004 seconds
Randomized with seed 76873 (jasmine --random=true --seed=76873)
Summary
In this post, we took a look at a simple example of JS code and how it can be covered by unit tests. You can find the complete code example on GitHub.
Subscribe to my newsletter
Read articles from Marcin Wosinek directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Marcin Wosinek
Marcin Wosinek
I'm JS developer with 13 years of professional experience. I'm always happy to teach my craft.