Joe Innes

A collection of 2 posts

Javascript Testing For Idiots Who Don’t Understand Anything They’ve Read About It So Far

So, I’ve heard a lot about ‘testing’, and why it’s a great thing, and I’m fully on board. I’ve read all the ‘red, green, refactor’, I understand the main principles behind Test Driven Development, and I’ve wanted to start for months.

But, I’ve never been able to wade through the hipster coffee shop menu of tools that will help me to do my tests. I tried to write my own ‘testing’ module, and it looked a bit like this:

export default function(fn, arg, expected) {  
  return fn(arg) === expected;  
}

This was great, because I could write the following:

import Test from ‘./Test.js’;

function helloWorld(name) {  
  return "Hello " + name;  
}

var passing = Test(helloWorld, "Joe", "Hello Joe");  
if (passing) {  
  console.log('All tests passing!');  
} else {  
  console.log('One or more tests failed :(');  
}

I was very happy, because I’d written my own test. Then I realised that this would be no good for testing my great random number generator. So I iterated. Instead of passing in an expected value, I’d pass in a second function to check the result of the first! Obviously I’d have to pass in the arguments provided to the first function as well…

export default function(fn, arg, expected) {  
  let result = fn(arg);  
  let passing = expected(result, arg);  
}

Now, I could do the following:

import Test from ‘./Test.js’;

function getRandomNumber(max) {  
  return Math.floor((Math.random() * max) + 1);  
}

function isMyNumberRandom(number, max) {  
  if (isNaN(number)) {  
    return false;  
  } else if (isNaN(parseFloat(number))) {  
    return false;  
  } else if (!isFinite(number)) {  
    return false;  
  } else if (number > max) {  
    return false;  
  }  
  return true;  
}

var passing = Test(getRandomNumber, 5, isMyNumberRandom);  
if (passing) {  
  console.log('All tests passing!');  
} else {  
  console.log('One or more tests failed :(');  
}

Even better! So now, I can write one function to produce a value, and another to check that the value is what I want it to be! Then I wrote a function that took two arguments. Suddenly, I needed to rewrite my “Test” function again, and decided that there must be a better way.

What is out there?

There’s very little out there in terms of documentation or beginner tutorials for someone who wants to get started testing. There are a few tutorials, but they jump from step zero to step ten without really explaining what they’re doing.

The testing frameworks out there are just more advanced versions of what I wrote above.

OK, so they might have exciting names like Mocha, and Chai, and Jasmine, but basically, all of them are just clever implementations of the above, with a lot of the hard work done for you.

How do I get started?

That’s up to you, but I’ll let you know how I started: I cheated. I used Facebook’s create-react-app script to generate a client side app with everything pre-configured.

npm install -g create-react-app  
alias crap=create-react-app # This step is not strictly required...  
crap MyApp  
cd MyApp

Now you can run

npm test

and it will show you that you have one, passing test. You can open up the App.test.js file to work out what is your passing test, and you’ll see the following:

import React from 'react';  
import ReactDOM from 'react-dom';  
import App from './App';  
  
it('renders without crashing', () => {  
  const div = document.createElement('div');  
  ReactDOM.render(<App />, div);  
});

So what is this actually doing? The block of imports is showing us everything we need to run these tests (in this environment). We need React, obviously. We also need ReactDOM so React can interact with the DOM. Then finally, we need your component (which may have any number of its own imports).

Notice what we’re not importing:

  • special ‘test’ modules
  • ‘assertion libraries’ (whatever they are)
  • mocks, stubs, spies
  • any kind of virtual browser to run the tests.

These are already part of the implementation, all you need to do is write your tests, not configure the testing tools.

Let’s take a look in a bit more detail at the second part:

it('renders without crashing', () => {  
  const div = document.createElement('div');  
  ReactDOM.render(<App />, div);  
});

So, in the first line of this, we’re pretending to write real English, rather than code. This is fairly normal for tests, and helps to show what tests are failing later on. We then start an arrow function for your test. The test creates a div, and renders the component into it.

This type of test is known as a smoke test, a term which was originally used in plumbing — you’d light a fire underneath a pipe, and then watch for smoke leaking out of the system. The term was also used later (and it’s not clear whether there’s any direct relationship between the two uses) for testing electronics — plug it in, switch it on, and if smoke comes out, something went wrong. Whatever the root of this term in software development, what it basically means is an incredibly simple test to confirm that everything’s running more or less as expected, without having to do any careful analysis.

Although this is a great first test, I’m not actually a big fan of this as a demonstration, because this is more or less the only ‘test’ you’ll ever write like this. In loose terms, as far as I can work out, this whole thing is wrapped in a ‘try’ block, and if an error is thrown, the test fails, but you can’t really do anything clever with it apart from check for exceptions thrown by your app (which you’ll see in the console anyway).

Test Driven Development — For Real

So, the first step in TDD is to write a failing test. Well, theoretically. The first step really is to decide what you want to achieve. To keep things nice and simple, and to get started, I’m going to write a simple component which renders a card with a title.

So the first thing I’m going to need is a Card component. Remember the TDD mantra — red, green, refactor — so the first thing I need is a failing test. It’s up to you how you organise your tests, but so far I’ve been doing it as one ‘test’ file per component, so I created a new test file: Card.test.js

Red

In it, I imported React and the React DOM, as well as my component.

import React from 'react';  
import ReactDOM from 'react-dom';  
import Card from './Card';

OK, so, now I’ve got everything I need to start testing, so I’ll just write the same test as I had before (except with the ‘Card’ component). I’m actually going to add a wrapper around the whole thing which will show me what I’m testing — this is important when your tests have logical ‘categories’, like components.

describe('a card', () => {  
  it('renders without crashing', () => {  
    const div = document.createElement('div');  
    ReactDOM.render(<Card />, div);  
  });  
});

So, I run the test and sure enough, I get one passing test (for the app component) and one failing test (for the card component).

We can move on now and start writing code.

Green

You’re not reading this article because you want to learn React, so I’m going to assume you know how to write a simple component. The trick to ‘getting to green’ is to write the minimum amount of code possible to pass the test. Don’t get all fancy with state initialisation, or click handlers, or anything — your test only covers the existence of the component — so just write the simplest render function you can imagine:

render() {  
  return <div></div>  
}

Run your tests again — both are now passing!

Refactor

Well, there’s not really much that needs refactoring here, this is the simplest possible code, so let’s write a new test.

Red

First, let’s think about what functionality we want — we want a title to be displayed on our card in an h2 tag. Furthermore, we want to be able to pass in this title as part of a ‘data’ prop.

At the moment, we don’t have the libraries we need to actually read the component, so we’re going to use AirBnB’s enzyme utilities. First up, let’s install it:

npm install --save-dev enzyme

Enzyme has LOTS of utilities, and I have no idea what they all do, but the one I want is ‘shallow’, which will allow me to render a single component and check it for stuff. Here, I’m going to import it, and describe the test.

import { shallow } from 'enzyme';
  
describe('a card', () => {  
  it('renders without crashing', () => { ... });  
  it('displays a title when this is passed to it', () => {   
      
  });  
});

I haven’t actually written any test code yet, so if I run this test, it’ll report as passing. Although this is an extreme example, it demonstrates why it’s important to make sure you get a ‘red’ before you start coding.

So now I’m going to start writing the code for the test itself. First up, I’m going to define the ‘data’ prop which I’ll pass in to the component

const data = {  
      title: "Test title"  
};

Then, I need to write up what this is going to look like when it’s rendered correctly (in JSX)

const expectedResult = <h2>Test title</h2>;

Now, rather than rendering the component as I did before, I’m going to use Enzyme’s shallow utility, and I’m going to call this the ‘wrapper’ (because this is the ‘wrapper’ for the h2).

const wrapper = shallow(<Card data={data}/>);

The last thing I’m going to do is to write an assertion, which is more complicated than it sounds. It’s just a clever way of writing the test which is nice and easy to read:

expect(wrapper.contains(expectedResult)).toEqual(true);

This assertion is based on the Jest framework (which comes out of the box with create-react-app), and you can read more about its APIs here — basically, you write ‘expect’, then an expression, then you chain it to one of the methods that will compare the evaluated expression to something. The ‘wrapper.contains’ here comes from the enzyme’s shallow utility — you can read its API here.

So all together, my test looks like this:

it('displays a title when this is passed to it', () => {  
  const data = {  
    title: "Test title"  
  };  
  const expectedResult = <h2>Test title</h2>;  
  const wrapper = shallow(<Card data={data}/>);  
  expect(wrapper.contains(expectedResult)).toEqual(true);  
});

I run this test — and it fails. Great. Now you can write some more code!

Green

Again, write the absolute minimum code you need for the test to pass:

render() {  
  return <h2>Test title</h2>  
}

Refactor

OK, that feels bad. The test is passing, but I’m cheating! Get used to it. TDD is about doing the bare minimum. If you can pass the test, then you need to write another one. Don’t delete the old one though! It’ll be useful for making sure you’re not going backwards when you’re writing new code.

Red

I don’t want myself to cheat any more, so I’m going to recalculate the title every time I run the test:

it('displays an arbitrary title when this is passed to it', () => {  
  const randomString = Math.random().toString(36).replace(/\[^a-z\]+/g, '').substr(0, 5);  
  const data = {  
    title: randomString  
  };  
  const expectedResult = <h2>{randomString}</h2>;  
  const wrapper = shallow(<Card data={data}/>);  
  expect(wrapper.contains(expectedResult)).toEqual(true);  
});

Green

I might as well do it properly now…

render() {  
  return <h2>{this.props.data.title}</h2>  
}

Refactor

This will work, and it fits the requirement of the test. I can’t simplify this code any more, so I’ll move on. While I was rethinking this code though, I realised that if no data is passed to the component, it won’t even render — because it’ll try and access the ‘title’ property of ‘data’, which will be undefined. Time for a new test!

Red

it('renders, but is empty if it doesn't get any data', () => {  
  const expectedResult = <h2></h2>;  
  const wrapper = shallow(<Card />);  
  expect(wrapper.contains(expectedResult)).toEqual(true);  
});

Green

The simplest way I can think of is chaining up an if statement or two to pass this test:

render() {  
  if (this.props.data && this.props.data.title) {  
    let title = this.props.data.title;  
    return <h2>{this.props.data.title}</h2>  
  } else {  
    return <h2></h2>;  
  }  
}

Refactor

The code above works well, but won’t be too useful in future in case I want to render anything else (like a description) onto the card. Instead, I’m going to put the whole thing in a try/catch block. That way, I can add in additional things that will be required to the try section without writing lots more ugly if statements.

render() {  
  try {  
    let title = this.props.data.title;  
    return <h2>{this.props.data.title}</h2>  
  } catch (err) {  
    return <h2></h2>;  
  }  
}

Conclusion

So that’s more or less it. Lather, rinse and repeat. Keep iterating — think of a feature, add a failing test, and then add code to pass the test. It’s nowhere near as complicated as it was when I first started reading about it. By now, you should have a nice set of passing tests to get you started, and a much better idea of the ‘hows’ of testing.


Designing My Web App

I’ve found a gap in the market. This is going to earn me billions. I’ve already chosen my Ferrari. All I have to do now is actually make the damn thing.

What is it?

It’s an employee training tracker. Yes, we can use Excel spreadsheets, Access databases, and pen and paper, but it doesn’t do all the fun stuff I want it to do.

What fun stuff?

Glad you asked.

  • It’ll list trainings, summaries, time taken, prerequisites, whether they’re part of on-boarding or in-service training regimes (or both), and links to any resources needed to complete the training.
  • It’ll list trainees, the trainings they’ve completed, the trainings they haven’t yet completed, trainings they haven’t completed but they have completed all the prerequisites for, whether they’re a full employee or being on-boarded, how long it will take for them to enter service.
  • It’ll list trainers, and how many trainings have been completed in a particular week, along with what the training was and how long it took.
  • It’ll have fancy interfaces for setting new trainings, adding trainers and trainees, and dashboard views.

Who’s going to use it?

The application will be used by three distinct groups, but realistically, it is mostly a compliance and reporting tool. Trainees are unlikely to ever actually bother checking. The three groups are:

  • Trainees themselves — to check their progress.
  • Trainers — to access resources and log trainings delivered
  • Compliance officers and on-boarding managers — to check status dashboards and provide lists of trainings delivered for ISO9000/ISO9001 compliance

Why am I going to do this?

Well, put simply, it fills a gap. I am currently training new joiners at work, and find that our programme is not logically constructed, resources are all over the place, and reporting that a training has been completed is done via email. Hardly ideal, reliable, or thorough.

How am I going to do this?

Now for the fun question. The How. It will be a web application that interfaces with a backend API. The backend API will be simple, ArrestDB looks fine for my needs to begin with. The database it sits in front of will have three tables:

  1. Trainees
  2. List of trainings
  3. Completed trainings

It will have to provide lists and individual objects in JSON format. Each of these tables will have specific columns, to be defined definitely later.

The front-end will handle putting data into the databases. There will be forms to add a new trainee/trainer, a new training, and to record a training that was delivered. The form to add a new training will allow you to set any of the existing trainings as prerequisites. The form to record a training will ask whether this is an in-service training or on-boarding training, and allow the trainer to select from a list of trainees appropriately. The form to add a new trainee/trainer will capture the email address, name, and hire date.

There will also be a few reporting views. One will list all available trainings, and be filterable. One will list all registered users, also filterable. One will listed all completed trainings recently in two modes — sensible mode (i.e.: one row per training session, regardless of the number of attendees) and stupid mode (i.e.: one row per training session per attendee). One will be an employee overview, showing what trainings they have completed, and what they are eligible for.

I have decided to use React to implement the front-end JavaScript, although I’m more familiar with Angular. I will slap a Bootstrap front-end on it to begin with it, and worry about theming it later.

What about version 2.0?

Version 2.0 will feature passwordless logins, securing the front-end and the API using authentication tokens sent to the user’s registered email, and then stored locally on the browser. As hotdesking is common in my environment, 10 tokens will be valid at the same time, with the user able to revoke any token at any time, or all of them. Exporting to CSV will have been implemented. Theming will be implemented, as will branding.

There will be a “training planner”, where multiple employees can be selected, and a list of trainings for which they are all eligible calculated. There may be some exciting calendar integration, but no promises.

Wow, that sounds awesome, can I help?

Sure! I will publish the GitHub repository alongside a “first commit” post in the coming days. If you steal my idea and run with it on your own though, I’ll hunt you down.


Footnote: I know that “trainings” is not the correct English term. I’m from England, and spent two years teaching English. But I don’t live in the UK, and here in Budapest, “trainings” is perfectly fine — and it beats typing “training courses” every time.