Functional Programming Unit Testing in Node — Part 4: Concurrency, Compose, and Coverage

Jesse Warden
9 min readJun 22, 2018

Welcome to Part 4 where we show how to do concurrency which is a lot easier to get “for free” using pure functions, we compose both async and synchronous functions, we and utilize a test coverage report to where next to focus our refactoring and testing efforts.

Contents

This is a 6 part series on refactoring imperative code in Node to a functional programming style with unit tests. You are currently on Part 4.

  1. Part 1 — Ground Rules, Export, and Server Control
  2. Part 2 — Predicates, Async, and Unsafe
  3. Part 3 — OOP, Compose, Curry
  4. Part 4 — Concurrency, Compose, and Coverage
  5. Part 5 — Noops, Stub Soup, and Mountebank
  6. Part 6 — Next, Logging, and Conclusions

Compose Again

Assuming getUserEmail succeeded, we’ll have the user’s information so we can send an email. We now need to read the text email template to inject that information into. We’ll compose that readFile function we wrote. The existing code is imperatively inside the getUserEmail‘s then:

Let’s fix that and compose them together #connectDemTrainTrax:

Great! We even removed all the error handling as that’s built into our pure readEmailTemplate function.

Parallelism, Not Concurrency (Who Cares)

However, that’s one new problem; we need userInfo later on once we’ve gotten all the email info setup and ready. Since it’s only in scope for this function it’s now gone. One of JavaScript’s most powerful and taken for granted features, closures, we just threw out the window to remain “pure” for purity’s sake.

We can fix it, though, with one of JavaScript’s other features: non-blocking I/O. We can return 3 Promises and wait for all 3 to complete, and use all 3 values in the same function. It doesn’t matter if one takes longer than the others; Promise.all will wait for all 3 to be done, then give us an Array with all 3 values in order. If even 1 has an error, it’ll just pop out the .catch. This has 2 bad problems, but we’ll tackle that in another article. This also has the benefit of being faster in that we don’t have to wait for each in line, they all happen “at the same time Node style” which is not the same as “happening at the same time Elixir/Erlang or Go style” but that’s ok, we can get into the same dance club.

For now, we’ll refactor to:

Now we’re talking. Loading from an external web service, reading from a local file, and a synchronous function call all can happen “at the same time”, and we don’t have to worry about how long each one takes. We use Array Destructuring to get our arguments out. Note they come in the same order we put the functions into the Promise.all.

We now have our user’s email address, the text template to inject information into, along with the file attachments used for both in the same function scope.

Synchronous Compose

One thing to nitpick. Sometimes you refactor FP code for readability purposes, not just for the mathematical purity reasons. In this case, check out the 3 levels of nesting:

mapFilesToAttachments(filterCleanFiles(get('files', req)))

In imperative code, if you see if/then statements nested more than 2 levels deep, that tends raise concern. Developers are sometimes fine with creating that code to ensure they truly understand the different cases in playing with ideas, but once complete, they don’t like LEAVING it that way. Nested if statements are hard to read and follow. If you DO follow them, you can sometimes get a rush or high in “figuring it out”. That’s not the goal, though; nested if’s are considered bad practice.

For FP, deeply nested functions like this have the same problem. It’s compounded by the fact we attempted to use verbose names for the functions to make what they do more clear vs. short names. This ends up making the problem worse.

For Promises, it’s not so bad; you just shove them in the .then. But what about synchronous code?

You have 2 options:

  1. Simply wrap in them in a Promise; most promises except for a couple of edge cases are fine getting a return value of a Promise or a value as long as the value isn’t an Error.
  2. Use Lodash’ flow function, or Ramda’s compose.
  3. Use the pipeline operator.

Sadly, at the time of this writing, the pipeline operator is only at Stage 1 for JavaScript, meaning it’s not even considered a possibility for inclusion in the ECMA Standard yet. None of this code is asynchronous so we’ll use the Lodash flow (I like Ramda’s compose name better).

Let’s put the functions in order, just like we would with a Promise chain:

Note the use of get('files'). The get function takes 2 arguments, but we only supply 1. We know it’s curried by default, meaning it’ll be a partial application if we just say get('files'); it’s waiting for the 2nd argument. Once it gets that, it’ll search for the ‘files’ property on it, else give undefined. If it DOES find undefined, filterCleanFiles will just spit out an empty Array, and mapFilesToAttachments will spit out an empty Array when you give it an empty Array. Otherwise, they’ll get the good Array full of files, and both of those functions will do their thang.

See how we use curried functions that create partial applications to help compose other functions? I know… for you guys, not a good pickup line, but you never know, she me might be a Data Scientist who digs Scala. Or she’s lit and you look good and anything you say doesn’t really matter at that point. Either way, it’s alllll good.

Now to use that composed function, we take what we had:

And replace it with our composed function:

Much better eh? Speaking of lit, I’m feeling that hard root beer right now, but I STILL remember we need to unit test our composed function. Let’s do that… and with confidence because we already have 3 unit tested pure functions, and we composed them together with a Lodash pure function. DAT CONFIDENCE BUILDING! Also, you MAY have to install config and nodemailer: npm i config nodemailer and then require them up top. Also, depending on the order of functions, you may have to move some functions around giving while we’re creating pure functions, they’re defined IN an imperative way, and so order matters. i.e. you have to create the const app = express() first before you can app.post.

:: chink chunk :: NIIIIICCCE!

Composing the Promise Way

You can also just compose the Promise way, and they’ll work for Promise based functions as well as synchronous ones allowing you to use interchangeably. Let’s first delete all the no-longer-needed imperative code:

And we’ll take the remaining mix of synchronous and imperative code, and one by one wire together:

You hopefully are getting trained at this point to start noticing “globals in my function”. Note our current line of code is:

let emailBody = Mustache.render(template, value)

But nowhere in the function arguments do we pass the render function to use. Let’s quickly modify the ever-growing Express route function signature from:

const sendEmail = curry((readFile, config, createTransport, getUserEmail, req, res, next) =>

to:

const sendEmail = curry((readFile, config, createTransport, getUserEmail, render, req, res, next) =>

We’re already in a Promise at this point, so we can return a value here, or a Promise and we’ll be sure we can add another .then if we need to. One trick VSCode, a free text and code editor by Microsoft, has is highlighting variables. Before we shove this rendered email template variable in the Monad train, let’s see if anyone down the tracks needs it. We’ll select the whole variable, and watch how VSCode will highlight usage of it as well:

Crud… it’s a ways down, AND it’s mixed in with this emailService thing. Let’s highlight him and see where he’s grouped:

This’ll be tricky. Good news, rendering the email and loading the email service configuration can be done at the same time. Let’s keep that INSIDE the Promise now until we feel comfortable we no longer need the userEmailAddress, emailTemplate, fileAttachments in scope. A lot more pragmatic people would be fine with keeping the code this way, and using JavaScript’s built in feature of closures, and move on with life. However, imperative code is harder to test, and results in LONGER code vs. smaller, pure functions that are easier to test. You don’t always START there, though. It’s fine to write imperative, then write “kind of pure” and keep refactoring your way there. That’s part of learning, figuring our the idea of how your code should work, or both.

And we’ll clean up the code below to use our pure functions first imperatively:

… and then refactor to more functional:

Note the fileAttachments comes from the scope higher up. The sendEmailSafe function requires a nodemailer transport. We create that from our function that creates the Object from the emailService. Once created we need that sendEmail function to pass it to the sendEmailSafe so we just immediately go .sendEmail in the first parameter. The createMailOptions is another function that simply creates our Object from the emailService object, the rendered via Mustache emailBody, and the virus scanned fileAttachements. One last touch is to remove the squiggly braces {} as we’re no longer writing imperative code, and the return statement as Arrow functions have an implicit return when you remove the squiggly braces.

This last part is left over from the callback:

), reason => { return next(reason) })

Typically you defer Promise error handling higher up the call stack; meaning, “let whoever is calling me deal with error handling since Promises that call Promises have their errors propagate up”. That’s fine, so… we’ll delete it.

After all that refactoring, here’s what we’re left with:

Coverage Report

Let’s unit test it; it’ll be hard because we have a lot of stubs, but we can borrow from the ones we’ve already created in the other tests. I’m not going to DRY the code at all in the tests as that would require too much brainpower at this point, but when you get an Agile Sprint or time to pay down technical debt, this is one of the stories/tasks you add to that list.

… before we do, let’s run a coverage report to see how much work we have cut out for us (we’re ignoring my fake npm module and the user stuff for now). Run npm run coverage && open coverage/lcov-report/index.html:

And the details around our particular function:

Status Quo at This Point

Wonderful; the only thing we need to test is the composition of those functions in Promise.all. Rather than create 20 billion stubs, and ensure they’re setup “just so” so the sendEmail unit test passes or fails, we’ll continue to our strategy of pulling out teency pieces, wrapping them in pure functions, testing those, repeat. Let’s start with the first Promise.all:

Then we’ll unit test the getEmailTemplateAndAttachments (he’ll end up ensuring we’ve tested the new getSessionIDFromRequest):

And we’ll then swap it out for the raw Promise.all:

… and then re-run coverage. Just run npm run coverage and you can refresh the coverage in the browser:

As you can see, coverage isn’t going to let us off that easy. That’s ok, we can re-use these stubs for the final battle. Let’s do the last Promise.all.

And swap it out:

Originally published at jessewarden.com on June 22, 2018.

--

--

Jesse Warden

Software, Amateur Powerlifter & Parkourist, married to @UXBrandy