Writing clean JavaScript tests with the BASIC principles

Hold your BASIC shield armor while The JavaScript test bastards are coming

Why do many teams abandon testing

What should my team do differently?

The BASIC acronym

6 repeatable parts in every test

Real-world example: A nasty test turns into a beautiful one

The function under test

Transforming into a beautiful test with the BASIC principles

Line number 2: Ambuiguiy


test(‘Should fail’, () => {
describe(‘transferMoney’)// The operation under test{  test(‘When the user has not enough credit, then decline the   request’, () => }

Line number 3: Mystery

const transferRequest = testHelpers.factorMoneyTransfer({}); 
const transferRequest = testHelpers.factorMoneyTransfer({credit: 50, transferAmount: 100});//Explictily specified the fields that matter here

Follow me on Twitter

Line number 4: Coupling

serviceUnderTest.options.creditPolicy = ‘zero’; 

Line number 5: Magic numbers

 transferRequest.howMuch = 110; // 
const amountMoreThanTheUserCredit = 110;
transferRequest.howMuch = amountMoreThanTheUserCredit;
transferRequest = {credit:50, howMuch: 100}

Line number 6: White-box testing

const databaseRepositoryMock = sinon.stub(dbRepository, ‘save’);
//This was meant to listen to the ‘save’ function call and ensure it was never called because the transfer should fail
// Assert — Check that the transfer was not saved using the public API
const senderTransfersHistory = transferServiceUnderTest.getTransfers(transferRequest.sender.name);
expect(senderTransfersHistory).not.toContain(transferRequest);

Lines 8–10: Not relevant

expect(transferResponse.currency).toBe(‘dollar’); 
expect(transferResponse.id).not.toBeNull();
expect(transferResponse.date.getDay()).toBe(new Date().getDay());

Lines 11: Implementation details

expect(serviceUnderTest.numberOfDeclined).toBe(1);

Lines 16–17: Overlapping

// Let’s get the user transfer history 
const allUserTransfers = serviceUnderTest.getTransfers(transferRequest.sender.name); expect(allUserTransfers).not.toBeNull(); // ❌ Overlapping expect(allUserTransfers).toBeType(‘array’); // ❌ Overlapping
 expect(senderTransfersHistory).not.toContain(transferRequest);

Lines 20–26: Sighhh imperative code

//Assertion: check that declined transfer is not in user history //array ❌
let transferFound = false;
allUserTransfers.forEach((transferToCheck) => {
if (transferToCheck.id === transferRequest.id) {
transferFound = true;
}
});
expect(transferFound).toBe(false);
expect(senderTransfersHistory).not.toContain(transferRequest);

Few words before we say goodbye

Other good reads

My testing workshop and courses

✨ Nodeconf — 5 hours advanced Node.js testing workshop next week

My online Node.js testing course

Follow me on Twitter

Unlisted

--

--

Software Architect, Node.JS Specialist. Consultant, blogger, conference speaker, open source contributor — author of the largest Node.js best practices guide

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Yoni Goldberg

Yoni Goldberg

2.7K Followers

Software Architect, Node.JS Specialist. Consultant, blogger, conference speaker, open source contributor — author of the largest Node.js best practices guide