protips

Logo

This site lists the protips that we shared with students during our courses

View the Project on GitHub appliedtechnology/protips

TDD-ing a whole (backend) application

This entire blog post will be recorded as a screencast here. It’s about an hour so bring coffee…

Testdriven development is a wonderful tool that helps us write better code and as an interesting side-effect, we get tests that help us to know if we break something later.

However - TDD outside a simplistic kata or for just a few functions can be hard to know how to get going with. For example; how do I TDD a web API or the inner workings of a single page application. This post tries to give you some clues to get started with this type of task.

Before I start the content there’s a disclaimer - writing a blog post about more advanced cases is always a trade-off. On the one hand, a full case would be many pages of material where most of it (linting, logging, deployment, scalability, usability tests, and advanced business logic) is irrelevant for the point I want to make.

On the other hand - excluding parts stands the risk of not being real-world enough.

I’m going to go for the simplistic version here and hope that you can take the next steps by yourself. But beware this post is mighty long - since I’m going through every step I’m taking, in doing so I’ve made some cuts to how advanced the business logic, infrastructure setup is, etc. to make my points about TDD clearer.

The case

That said - let’s write a simple HTTP endpoint that lists people in a family. The endpoint is located on /api/family/:familyName . We will store all the families in a JSON-file.

Here’s my family as an example:

{
  "families": [{
    "name": "hammarbergs", 
    "members" : [
      { "name": "Marcus", "birthYear": 1972 },
      { "name": "Elin", "birthYear": 1977 },
      { "name": "Arvid", "birthYear": 2010 },
      { "name": "Gustav", "birthYear": 2010 },
      { "name": "Albert", "birthYear": 2008 }
    ] 
  }]
}

Accessing the JSON file will be hidden in a json-client.js-file - to mimic looking up data from a database. Normally this would be accessing a MongoDb or a third party web storage, but we will expose a simple getAll function.

To add some business logic to this let’s calculate their age, at the time of the request, and return objects that looks like this:

[
  { "name": "Marcus", "age": 48 },
  ...
]

Ok - that’s what we’re gonna build.

Some tools to our disposal

The main trick in TDD is to write the test before we write the code (see this post on more the good thing that follows). It’s that simple, to be honest, but it’s also there it breaks down when you are doing something a little bit more advanced. Just getting to the point where you can write the test can be hard. Indeed entire books has been written on this topic

We will use a few tools to help us get to that point and make our code be in a state where we can test it:

On the more technical side our tools will be:

That’s some mighty good tools - I feel confident.

The steps

  1. Write an initial e2e test with supertest
    1. Watch it fail
    2. Set up an express server that returns hardcoded data
    3. Run the test and watch it pass
  2. Write the inner workings - units. Iterate over:
    1. Add a unit test
    2. Watch it fail
    3. Hard code correct values
    4. Implement correct code
    5. Watch it pass
  3. Run e2e test again - watch it pass

Wow - that will be amazing once it works. If you want a challenge, try it yourself based on this list of steps.

Let’s hop to it.

Write an initial e2e test with supertest

(We are going to use supertest to issue our request - this is not true end to end which would require us to deploy to a real server, etc but it’s e2e enough to show my point, I hope you agree)

If we were to follow the test-first approach to the letter this is where we would get stuck. There are no tests to run. And I would fail the first principle of You can't write any production code until you have first written a failing unit test. But I can write some test code…

Let’s set up the initial project structure, just enough to be able to write a failing e2e test.

mkdir tddalltheway && cd tddalltheway
npm init -y
npm i mocha supertest -D
touch e2e-tests.js

(I’m scoping out linting now - shame on me but see above)

Add a test script for e2e-tests:

"scripts": {
  "e2e": "mocha e2e-tests.js"
}

And now we can run our e2e-tests: npm run e2e . 0 tests are passing, unsurprisingly since we haven’t written any. But let’s do that. Let’s get to our first failing test.

I’m thinking that we could just set up a call to /api/family/hammarbergs and expect 200, as a start.

const request = require('supertest');
const app = require('.').app;

describe('The /api/family end point', () => {
  it('returns 200 for GET', done => {
    request(app)
      .get('/api/family/hammarbergs')
      .expect(200, done);
  });
});

Something like that. Let’s watch it fail! Can we get to a place where we are allowed to write any production code.

Watch it fail

npm run e2e and YES it fails. We get an error, that is the JavaScript version of compilation error. Our code cannot be put together.

Error: Cannot find module '/Users/marcus/temp/tddalltheway/index.js'

The second principle of TDD says: You can't write more of a unit test than is sufficient to fail, and not compiling is failing.

Perfect we are there. So let’s make it compile. Simply, I think.

touch index.js
npm run e2e

Yes, perfect. That too fails, but with another message:

  1) The /api/family/hammarbergs endpoint
       returns 200 for GET:
     TypeError: Cannot read property 'address' of undefined
     ...
     at Context.<anonymous> (e2e-tests.js:7:8)

it’s a bit convoluted to understand maybe, but this is since we are on a high-level testing end-to-end. The error comes from line 9 in our code (as you can see in the error message) and there we find (line 6-7 for context):

    request(app)
      .get('/api/family/hammarbergs')

app there refers to the app object exposed by const { app } = require('.');. In short - there’s nothing in our index.js-file. Especially nothing that supertest can make sense of.

We are now on Red (failing test) and it’s perfectly fine for us to write the smallest possible implementation to get to Green. Without changing the tests that would mean:

Set up an express server that returns hardcoded data

I’m gonna play it stupid for awhile. Bear with me…

Let’s add Express as a runtime dependency to our app, and then rerun the test and see if it helped. Doing the smallest possible thing, taking baby-steps

npm i express
npm run e2e

No - that gives us the same error.

Let’s now add some code, to set up the smallest possible Express server, to pass our test. This is a simple example:

const express = require('express');
const app = express();

app.get('/api/family/:name', (req, res) => res.sendStatus(200));

const port = 3000;
app.listen(port, () =>
  console.log(`Example app listening at http://localhost:${port}`)
);

Straight off the documentation.

Being very nervous TDD:ers we run again npm run e2e.

Two bad things happen now… First, we get the same error TypeError: Cannot read property 'address' of undefined and secondly, the tests hang…

That the tests hang feels really bad to me so let’s fix that first. This has to do with the fact that we are always starting the express server in listening mode. It’s sitting there serving any request that comes to it. Perfect - unless it was for the fact that we want supertest to issue these requests for us. This is a bit beyond the scope of this post - but I’ve written about it here and the hack is simple enough.

Let’s check if the module has a parent, which it does in the case of a supertest wrapping it.

if (!module.parent) {
  const port = 3000;
  app.listen(port, () =>
  console.log(`Example app listening at http://localhost:${port}`)
  );
}

Sorry about all this fiddling but since we are testing e2e we have some setup-code to take care of. This is one of those issues.

Ok - back to something sane. Let’s re-run the tests npm run e2e.

Perfect - the test fails and then stops. Now, why did it fail? Ah - same error.

Another setup issue for supertest - we need to expose the app object from Express, so that supertest can request(app).get() to make requests to it. That too is a setup issue, and easy to fix. Here’s the final initial version of the code:

const express = require('express');

const app = express();
module.exports.app = app;

app.get('/api/family/:name', (req, res) => res.sendStatus(200));

if (!module.parent) {
  const port = 3000;
  app.listen(port, () =>
  console.log(`Example app listening at http://localhost:${port}`)
  );
}

Run the test and watch it pass

I know it’s final because when I run npm run e2e now it passes! WONDER AND AMAZEMENT…

But that’s now what we wanted, now is it. We wanted a nice array of people in my family and their ages.

And now we’re on stupid Green. We cannot write more production code - we need more tests.

Let’s express what we mean by return a nice array of the people in my family and their ages but in JavaScript, using supertest:

it('returns a nice array of the people in my family and their ages', done => {
    request(app)
      .get('/api/family/hammarbergs')
      .expect(res => {
        const family = res.body;
        assert.strictEqual(family.length, 5);
        const firstPerson = family[0];
        assert.strictEqual(firstPerson.name, 'Marcus');
        assert.strictEqual(firstPerson.age, 48);
      })
      .expect(200, done);
  });

Ok - that’s a bit more complicated but also code so it’s strict. It also contains quite a lot of assumptions, for example, that my family members are returned in a certain order at a certain date. But hey - let’s scope that out for now to continue.

This shows what we mean by getting data back from this endpoint. Good enough

Let’s run the tests npm run e2e. BAH - “compliation” error ReferenceError:assert is not defined. Let’s fix that by adding const assert = require('assert'); at the top of our file.

Rerun - and new errors. Hurrah! That’s more like it:

  1) The /api/family/hammarbergs endpoint
       returns a nice array of the people in my family and their ages:
     AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

undefined !== 5

Let’s fake that and see that it passes:

app.get('/api/family/:name', (req, res) => {
  const family = [
    { name: 'Marcus', age: 47 },
    { name: 'Elin', age: 43 },
    { name: 'Arvid', age: 10 },
    { name: 'Gustav', age: 10 },
    { name: 'Albert', age: 12 },
  ];

  res.json(family);
});

Running the test and … FAIL?! Ah, me stupid. I always forget my age. I wrote the wrong data in the faked version… Sorry about that. Updating { name: 'Marcus', age: 47 }, and rerunning the tests. It passes.

Thank you, Lord, of TDD, for automated tests that catch my stupid mistakes.

Break and reflection

Let’s take a little reflection break.

Where are we now?

We’re in a pretty great spot

There is stuff left to do, as all of it is hardcoded, but we are in a pretty nice spot.

Being on Green also means that it is safe to refactor our code. If we break something we will know.

My plan now is to do just that - and as part of that start to write unit tests for the things that I refactor.

I’m now leaving the e2e-test that will act as a safeguard for me and going deeper into the code.

Break over - there’s code to be written!

Write the inner workings - units

First, before we leave green - let’s refactor a bit. Let’s move that family array to a function. I started the e2e test with a watch while I did this: npm run e2e -- -w, just to catch any mistakes I did.

const getFamily = () => [
  { name: 'Marcus', age: 48 },
  { name: 'Elin', age: 43 },
  { name: 'Arvid', age: 10 },
  { name: 'Gustav', age: 10 },
  { name: 'Albert', age: 12 },
];

app.get('/api/family/:name', (req, res) => {
  const family = getFamily();
  res.json(family);
});

That works, and but didn’t do much. Also, we are not using that :name parameter. Finally - we are now hard-coding the age there…

If only… if only … if only there was a method that read a JSON- array of families and filtered it by name and then calculated… It could look something like:

const familyGetter = require('./family')
familyGetter.getFamilyWithAges('hammarbergs', 2020)

and return a nice array.

I use this way of thinking a lot when I write code. I call it wishful coding. It’s a good start for writing tests.

We could, if we wanted, actually plug this into our current structure and call into a version that returns faked data, while still on Green. It would look something like this:

// in a family.js
module.exports.getFamilyWithAges = (name, year) => [
  { name: 'Marcus', age: 48 },
  { name: 'Elin', age: 43 },
  { name: 'Arvid', age: 10 },
  { name: 'Gustav', age: 10 },
  { name: 'Albert', age: 12 },
];

// in index.js
app.get('/api/family/:name', (req, res) => {
  const family = familyGetter.getFamilyWithAges(req.params.name, 2020);
  res.json(family);
});

But that is maybe moving a bit to fast. I feel the need for new tests. Unit tests.

I’m going to leave the e2e test passing like this and now start to write a little API that my route will use.

Write a test

First we need a failing unit test. Let’s set that up now. Create tests.js file and add the following:

const assert = require('assert');
const sut = require('./family');

describe('Family getter unit tests', () => {
  it('has a function getFamilyWithAges', done => {
    assert.notEqual(sut.getFamilyWithAges, undefined);
    done();
  });
});

Then add a script to run the unit tests:

  "scripts": {
    "e2e": "mocha e2e-tests.js",
    "test": "mocha tests.js",
    "test:watch": "npm t -- -w"
  },

Watch it fail

Ok - we know this. Let’s run the tests. Ah, let’s run it with a watch while we’re at it npm run test:watch.

It fails! As we though. There’s no family.js-file. Let’s create one and speed up a bit by exporting a getFamilyWithAges function:

const getFamilyWithAges = () => {};

module.exports = {
  getFamilyWithAges,
};

Now the first test pass, but it doesn’t do what I want. Let’s create a test that, first of all, passes in the parameters I expect and then returns some hard-coded data. Much like the e2e-test, but now on the unit level

it('returns 5 hammarbergs', done => {
    const family = sut.getFamilyWithAges('hammarbergs', 2020);
    assert.strictEqual(family.length, 5);
    done();
  });

Perfect - it fails for the right reason. Let’s make it pass

Make it pass

The easiest way to make this pass is just to return the fake version of the family again:

// in family.js
const getFamilyWithAges = () => [
  { name: 'Marcus', age: 48 },
  { name: 'Elin', age: 43 },
  { name: 'Arvid', age: 10 },
  { name: 'Gustav', age: 10 },
  { name: 'Albert', age: 12 },
];

Bom - it’s green…

Notice how it’s hardcoded and doesn’t take any of the parameters into consideration. We just made it pass.

Make it good

This next step is a bit trickier because here’s a trade-off; we can take longer steps and make a lot of refactorings towards the goal I might have (I’m thinking to move the data into a separate file and then use a “3rd party” library to read it) or do that in smaller steps.

Let’s go slower - but then you will have to promise me to now nag me about not having the correct headings.

Round and round we go

Because now we will go Red-Green-Refactor much faster.

First - let’s move the object out and create the proper structure for it, with a list of families with name and members:

const families = [
  {
    name: 'hammarbergs',
    members: [
      { name: 'Marcus', birthYear: 1972 },
      { name: 'Elin', birthYear: 1977 },
      { name: 'Arvid', birthYear: 2010 },
      { name: 'Gustav', birthYear: 2010 },
      { name: 'Albert', birthYear: 2008 },
    ],
  },
];

const getFamilyWithAges = () => families[0].members;

Ok - still green. I’m gonna count that as a refactoring. Now let’s make a failing test that ensures that we get the correct family back.

it('returns null for non-existing family', done => {
  const family = sut.getFamilyWithAges('family name that does not exists', 2020);
  assert.strictEqual(family, null);
  done();
});

This test passes but for the wrong reason. I didn’t Watch it fail. That is a false positive because I wanted to see it fail. My code needs work.

const getFamilyWithAges = familyName => {
  const familyForName = families.find(f => f.name === familyName);
  return familyForName.members;
};

This takes me to Red, for the right reason - my second test is now failing since familyForName will be undefined with the family name family name that does not exist.

Let’s fix that by failing fast

const getFamilyWithAges = familyName => {
  const familyForName = families.find(f => f.name === familyName);
  if (!familyForName) return null;
  return familyForName.members;
};

Ok - that’s great.

Let’s turn our attention to the age calculation. Let’s imagine that this was hard - then I would break this out into a separate function and test it separately. I’ll spare you the details but…

describe('Age calulcator', () => {
  const ageCalulator = require('./ageCalculator');
  it('born 1972 at 2020 = 48', () => {
    const age = ageCalulator.caluclateAge(2020, 1972);
    assert.strictEqual(age, 48);
  });
  it('born 1900 at 1901 = 1', () => {
    const age = ageCalulator.caluclateAge(1901, 1900);
    assert.strictEqual(age, 1);
  });
  it('born 1900 at 1900 = 0', () => {
    const age = ageCalulator.caluclateAge(1900, 1900);
    assert.strictEqual(age, 0);
  });
  it('throws for negative results', () => {
    assert.throws(() => {
      ageCalulator.caluclateAge(1900, 1901);
    }, new Error('Negative age'));
  });
  it('throws for non-numbers', () => {
    assert.throws(() => {
      ageCalulator.caluclateAge('apa', 'banan');
    }, new Error('Need years as numbers'));
  });
});

// ageCalulcator.js
module.exports.caluclateAge = (year, birthYear) => {
  if (isNaN(year) || isNaN(birthYear)) {
    throw new Error('Need years as numbers');
  }
  if (year < birthYear) {
    throw new Error('Negative age');
  }
  return year - birthYear;
};

I caught at least 2 errors with those tests. Just sayin’

And now I’m back on Green and can write a test to ensure that age is calculated correctly:

it('calculate age for hammarberg #1 correctly', done => {
    const family = sut.getFamilyWithAges('hammarbergs', 2020);
    assert.strictEqual(family[0].age, 48);
    done();
  });

Perfect - Red and I understand why undefined !== 48 since I’m not returning an object with an age-property. Let’s fix it with a .map

const getFamilyWithAges = familyName => {
  const familyForName = families.find(f => f.name === familyName);
  if (!familyForName) return null;

  return familyForName.members.map(m => ({
    name: m.name,
    age: 48
  }));
};

Green - let’s make it use our age calculator… which will reveal a weakness in our code:

const getFamilyWithAges = familyName => {
  const familyForName = families.find(f => f.name === familyName);
  if (!familyForName) return null;

  return familyForName.members.map(m => ({
    name: m.name,
    age: age.caluclateAge(2020, m.birthYear),
  }));
};

See that hardcoded 2020. Realize that it will not be better to new Date().getFullYear() as that will make our tests break when everyone is a year older…

But we are on Green now - time to refactor. And the refactoring is a half-in place already. All our tests are passing in a year (const family = sut.getFamilyWithAges('hammarbergs', 2020);) - let’s use it.

const getFamilyWithAges = (familyName, currentYear) => {
  const familyForName = families.find(f => f.name === familyName);
  if (!familyForName) return null;

  return familyForName.members.map(m => ({
    name: m.name,
    age: age.caluclateAge(currentYear, m.birthYear),
  }));
};

BOM! Green again.

Injecting 3rd party service

The one thing that is bad now is that this array of families is hardcoded… We wanted that in a JSON-file and then something that read the data for us. Abstracted away nicely in a .getJSON function. So I made one up for us, which will act as an example third party service. I’m calling it jsonClient and we can use it like this:

r
  .getJSON('./families.json', 'families')
  .then(data => console.log(`${data} found in the 'families' node`))

That will return a promise with the data from the families node. Perfect for us.

Now - let’s use it in our family.js module. The jsonClient module is now a dependency for the family-module. family cannot work without a jsonClient. One common way to handle this is through Dependency Injection where we send the dependency for one module to it, as we create the module.

There are a few approaches to do this:

Quite simply, we will change the getFamilyWithAges function so that it takes a jsonClient parameter. Now, this is interesting because that will break all our tests… and we have just change production code without a failing test. So I’m going to go the other way - let’s change one test and then watch it fail - let’s take the one that we just wrote and change it

it('calculate age for hammarberg #1 correctly', done => {
  const mockJsonClient = {
    getJSON: () =>
    new Promise((resolve, _) => {
      resolve({
        name: 'hammarbergs',
        members: [
          { name: 'Marcus', birthYear: 1972 },
          { name: 'Elin', birthYear: 1977 },
          { name: 'Arvid', birthYear: 2010 },
          { name: 'Gustav', birthYear: 2010 },
          { name: 'Albert', birthYear: 2008 },
        ],
      });
    }),
  };
  const family = sut.getFamilyWithAges(mockJsonClient, 'hammarbergs', 2020);
  assert.strictEqual(family[0].age, 48);
  done();
});

Ok - that got quite a bit longer, but bear with me. Right now I’m trying to get to Red. I’ll fix this later.

Notice that it is returning a promise - since my real implementation is returning a promise…

Running the test and … yes, it breaks. But in a very strange way (TypeError: Cannot read property '0' of null) which has to do with the fact that we are passing in the wrong thing and … let’s fail just one test and sort this out in small steps.

A well-place .only and now I have that one test failing. Red - so let’s make that one failing test pass, but let’s be smart about it and just refactor the code to use the passed in JSON-client.

const getFamilyWithAges = async (jsonClient, familyName, currentYear) => {
  const families = await jsonClient.getJSON('./families.json', 'families');
  const familyForName = families.find(f => f.name === familyName);
  if (!familyForName) return null;

  return familyForName.members.map(m => ({
    name: m.name,
    age: age.caluclateAge(currentYear, m.birthYear),
  }));
};

It just took one extra line, after I passed in the jsonClient. I used async/await to await the result of the promise from .getJSON , instead of the .then-syntax. This will affect the tests later…

Or now, actually. Because the test failed again (TypeError: Cannot read property 'age' of undefined) - we’ll have to make the test await the, now, async method. It looks something like this:

it.only('calculate age for hammarberg #1 correctly', async () => {
  // all the code from above
  // but notice that done() is removed
});

Ok - after that we are on Green, using a mock, but promise version, for the JSON client

There’s one explanation and one disclaimer to be made here. Let’s do the disclaimer first.

Disclaimer Notice how I have hard-coded the path to ./families.json in the code there. Typically bad practice. This is where I would have backed up and made the whole module into a class that took a JSONClient as an in parameter with the path set. Or maybe rewritten the JSONClient so that it takes the path to the JSON file in its constructor. But I’ll leave this for now and let the reader make it work better, should you want to.

As for the explanation - notice, in the test, how my mockJsonClient doesn’t care about any of the parameters to the getJSON-function and always returns the same thing; an array with one single element - the hammarbergs. That’s on purpose and very much a thing we can do with a mock. It simply serves the purpose of a fake version of the real JSONClient that we want to call in this particular test.

In this case, I’ve written the mock myself, right in the test, but we could also have used a mocking framework like JS Mockito or Sinon instead. They could have helped us to write the mock-code more elegantly, and also written assertions about the method being called a certain number of times, etc.

For the most part, I find that these simple, write-it-yourself mocks works to get started with.

Where were we - oh that’s right. We have one test running and on Green. .only the test is passing, but that’s ok for now. Let’s make some refactoring and then make the others pass one by one, in our new code.

First, we can now take out the families-array from the family.js module. Nice, because that felt strange. After that, the test still passes.

Then we can run another test and ensure that it works too. Let’s add it.only to the it.only('family name that does not exists') test.

That breaks, because jsonClient.getJSON - we are not passing in a jsonClient in that test. Let’s first make it green - this time by fixing the test (!):

it.only('family name that does not exists', async () => {
  const mockJsonClient = {
    getJSON: () =>
    new Promise((resolve, _) => {
      resolve([
        {
          name: 'hammarbergs',
          members: [
            { name: 'Marcus', birthYear: 1972 },
            { name: 'Elin', birthYear: 1977 },
            { name: 'Arvid', birthYear: 2010 },
            { name: 'Gustav', birthYear: 2010 },
            { name: 'Albert', birthYear: 2008 },
          ],
        },
      ]);
    }),
  };
  const family = await sut.getFamilyWithAges(
    mockJsonClient,
    'family name that does not exists',
    2020
  );
  assert.strictEqual(family, null);
});

That works but now I feel very queasy about duplicating that much code. Some of it is my name, even. Luckily we are on Green and can fix that.

I’m going to set up the mock JSON Client in a beforeEach-function:

describe('Family getter unit tests', () => {
  let mockJsonClient = {};
  beforeEach(() => {
    mockJsonClient = {
      getJSON: () =>
        new Promise((resolve, _) => {
          resolve([
            {
              name: 'hammarbergs',
              members: [
                { name: 'Marcus', birthYear: 1972 },
                { name: 'Elin', birthYear: 1977 },
                { name: 'Arvid', birthYear: 2010 },
                { name: 'Gustav', birthYear: 2010 },
                { name: 'Albert', birthYear: 2008 },
              ],
            },
          ]);
        }),
    };
  });
  ...

Like a madman, I keep running my test on every save and it works.

Let’s now replace the local variable in the two passing tests, with the mockJsonClient for the entire describe-block.

it.only('family name that does not exists', async () => {
  const family = await sut.getFamilyWithAges(
    mockJsonClient,
    'family name that does not exists',
    2020
  );
  assert.strictEqual(family, null);
});
it.only('calculate age for hammarberg #1 correctly', async () => {
  const family = await sut.getFamilyWithAges(
    mockJsonClient,
    'hammarbergs',
    2020
  );
  assert.strictEqual(family[0].age, 48);
});

That made the tests small and tight again. And no duplication. And still green.

Let’s now add another .only and fix the next one:

it.only('returns 5 hammarbergs with ages', async () => {
  const family = await sut.getFamilyWithAges(mockJsonClient, 'hammarbergs', 2020);
  assert.strictEqual(family.length, 5);
});

HA! Too simple. Just add it. I’m feeling confident. Let’s take out all the .only and then just run the entire suite fixing it as we go.

It went green - because the only test I had left was the test that checked that a function called getFamilyWithAges existed.

And with that our little family age getter function works as I want it too. We have tests that are not touching the file system. Let’s wire the whole thing together.

But first:

Run e2e test again

Ok, I ran my old npm run e2e script. But before I thought to myself:

This should fail - since I’m not passing in the correct things to the getFamilyWithAges function

BUT it passed!?

Because we are not using the family module at all. Let’s fix it.

So, let’s see what do we need:

Like this maybe:

const express = require('express');

const app = express();
module.exports.app = app;

const familyGetter = require('./family');
const jsonClient = require('./jsonClient');

app.get('/api/family/:name', async (req, res) => {
  const familyName = req.params.name;
  const year = new Date().getFullYear();
  const family = await familyGetter.getFamilyWithAges(
    jsonClient,
    familyName,
    year
  );
  res.json(family);
});

if (!module.parent) {
  const port = 3000;
  app.listen(port, () =>
    console.log(`Example app listening at http://localhost:${port}`)
  );
}

npm run e2e shows us that it works…

Reflection on orchestration

Let’s pause here and look at the index.js-file. It’s basically just a lot of wiring code… I can make the point clearer by writing the code like this:

app.get('/api/family/:name', async (req, res) => {
  const family = await require('./family').getFamilyWithAges(
    require('./jsonClient'),
    req.params.name,
    new Date().getFullYear()
  );
  res.json(family);
});

Yeah - that is all we are doing. Just wiring parts together - the real work happens in other parts of the code. And those we have tested already.

I’m not leaving it like that. Let’s revert and make the …

Final refactoring

Because that year thingy feels bad and is a leftover from earlier… I should have refactored this. Because my e2e-test will now fail if the test is run … next year for example. We can fix this in several ways:

I’m going with the first version here. It will look something like this in the index.js:

const getYear = () => new Date().getFullYear();
module.exports.getYear = getYear;

// using it in the endpoint... 
app.get('/api/family/:name', async (req, res) => {
  const family = await familyGetter.getFamilyWithAges(
    jsonClient,
    req.params.name,
    this.getYear() // important to use this.getYear()
  );
  res.json(family);
});

And then I’m overriding that function in the test:

const assert = require('assert');
const request = require('supertest');
const sut = require('.');

const { app } = sut;

describe('The /api/family end point', () => {
  beforeEach(done => {
    sut.getYear = () => '2012';
    done();
  });
  ...

It got a bit messy here since I previously only needed the app object - but now I’m getting the System Under test and then deconstructing the app from that constant.

Ok - anyway - now my tests can use another year. Let’s use one that has passed to test it. 2012 was a good year. I was 40. And the test fails - since I’ve previously tested it for 48 but in this fake world, I’m back to wonderful 40.

Fixing that test-data and I’m back on Green.

Folder structure

There are some folder structure things that I wanted to fix in the end too, now that we have so many files:

But that is easily and safely done now that we have tests to ensure that we don’t break anything. I’ll leave it as an exercise for you.

Try it. Take small steps and have the tests running all the time.

Maybe you’ll find some improvements along the way…

Summary

It is not only possible to write tests for all code we write but also a nice experience where we allow ourselves to take small steps through a larger problem.

Writing an end to end test (suite) before you start to flesh out the units and details helps you to guide you through the problem space.

The code is found here