# A study of Test Driven Development and Functional Programming in TypeScript

## Introduction

I come from an object-oriented programming (OOP) background - it was the hot stuff in my formative
years of learning to program - driven by C++ and Java. Embracing unit testing meant embracing
Inversion of Control (IoC) and Dependency Injection (DI). This was the way.

As my language of choice switched to TypeScript, and passing data as objects defined by interfaces
passed through HTTP APIs, real OOP classes fell away but I continued to use classes for DI. Business
logic was in service classes, which were really just collections of functions with shared DI in the
constructor. Unit tests mocked out all those dependencies, and with pride my coverage often hit
100%. I tested my code against unit tests *first*, so that everything usually worked upon first
deployment. I largely forgot how to use the IDE's debugger.

All was good.

I had heard the fans of Test Driven Development (TDD) exclaim the wonders of it, but figured my
*test before deploy* approach basically did that. (Turns out, not really.)

I had heard the fans of functional programming (FP) exclaim their superiority over OOP. But hey, I'm
just using classes to organize my functions for DI - so I was basically functional. (Yes, but no.)

I did not see the connection between these. I didn't fully understand either one, and I knew that.
Occasionally I'd read a blog article about TDD or FP to try to understand the excitement. I wanted
to find these techniques exciting! But everything I read was so basic that I couldn't see how to
apply it to real problems, and they didn't show how to connect the concepts. Or worse perhaps, TDD
was [completely misrepresented](https://www.jamesshore.com/v2/blog/2005/microsoft-gets-tdd-completely-wrong)
and looked horrible because doing it wrong *is* horrible.

I'd think: functional is great for *pure* functions, but how do you *test* the orchestration that
isn't pure?

## What really is Test Driven Development?

Robert C. Martin (Uncle Bob) covers a lot of ground about software development practices in his talk
about [Expecting Professionalism](https://www.youtube.com/watch?v=BSaAMQVq01E)
and specifically TDD [here](https://vimeo.com/97516288). (Watch these.)

The analogy to the accounting practice of double-entry bookkeeping especially resonated for me.
Double-entry bookkeeping is the discipline of keeping a transaction ledger for each account. When
you subtract from one account, it *must* be added to another - immediately. Often this other
"account" isn't one in the traditional sense, it is just a means of tracking and *checking your
work*.
It's a checksum. Subtract here; add there; compare the totals. If you make a mistake, the bookkeeper
immediately knows.

We often write all the code, and *then* write all our tests to check it.
Bookkeepers *don't*. They subtract an entry here, and add it there. It's an atomic operation. Why?
Because if they did all one and then the other and make a mistake, the checksum at the end is
simply "it doesn't match". Where is the mistake? It's *somewhere* in one of the *many* transactions
they added. That's painful, and entering all transactions atomicly is less so.

TDD is like double-entry bookkeeping. The rules are put in two places, atomically. Write test code,
then write some production code. Checksum. Write some test code, write some production code.
Checksum. It may sound painful, but if you introduce a bug you will know it *immediately*. It's the
ultimate linting tool. How painful is this compared to a bug in production? Once you get used to it,
not at all. This is the #1 TDD promise: Bug free code! (Caveat: You have to understand the desired
behavior, of course. Otherwise you'll write the wrong behavior twice.)

Here's the #2 TDD promise: Your code will be cleaner, easier to maintain, and self documenting.
Watch the videos and find the arguments for this promise.

If Uncle Bob and I finally convinced you that Test Driven Development (TDD) is *amazing*, follow it
up with Ian Cooper's dedicated talk on the topic:
[TDD, Where Did It All Go Wrong](https://www.youtube.com/watch?v=EZ05e7EMOLM) (Yes, this is actually
pro TDD! He addresses where people get it wrong.)

Next in my research, I discovered
[this](https://medium.com/spotlight-on-javascript/real-world-node-js-tdd-example-4f877a46e1f1)
guide. No longer a simplistic example. TDD and FP rolled up in an example that is big enough to hit
real challenges, and explained progressively rather than just an end result. But.... well... what's
with all the factory functions? Everything is a factory now. 😬

That last article linked to
this [original guide to testing without mocks](https://www.jamesshore.com/v2/blog/2018/testing-without-mocks)
. Here's another concept that ties in neatly with TDD, but this one is using classes instead of
functions. The classes though, still involve factories. Also, the examples are showing React code -
reminding me that React components themselves use the factory pattern, whether functional or
class-based. I don't know why he wants to go *entirely* without mocks (to the point of running a
fake server in-process - just mock it already!), but the takeaway is that mocking (and faking) can
be a
final option rather than the first go-to choice.

## What is Functional Programming?

Go full functional, or use classes? Either work in JavaScript, to an extent,
but that's something standing out in
these examples: they are both JavaScript not TypeScript. To some extent, I can see that powerful
argument for strong typing is weakened when doing proper TDD: failures happen by default. That makes
me think maybe I can allow implicit `any` (turn off that eslint rule) and not define types for
*everything*, but there are still benefit to strong types. Are types more difficult with FP? There
does appear to be a correlation between FP and going full dynamic typing. 🧐

When to use class vs function? [This article](https://labs42io.github.io/clean-code-typescript/)
about applying [CleanCode](http://cleancoder.com/) to TypeScript helped me realize it is not an
either-or choice. Looking at the patterns, I realize I can *lean* functional, but enjoy class and
real OOP goodness where it makes sense. In short: classes are for object-oriented logic, modules are
for grouping related functions.

*But wait*, you a FP practitioner shouts! *That isn't what functional programming is about!*
Yes, [Uncle Bob](https://vimeo.com/97514630) has set me straight in his talk about Functional
Programming. Actual FP is about being stateless. A Pure Function is one with no side effects - given
the same input it will always have the same output. Pure. Testable. Stateless. Often recursive.
In fact, FP purists tell us that functional has no state, even local variables, and no loops;
recursion
takes the place of both and for this not to be a poor developer experience you need a language
designed for this. (Lisp, Haskell, Clojure, ...)
Simply using just functions instead of classes doesn't automatically mean FP. Really,
applications can't be entirely FP; only functions can be pure. An application with no state is just
a big function and has pretty limited use. Also, we're talking about JavaScript here,
which [not a real FP language](https://en.wikipedia.org/wiki/Functional_programming),
but rather a hybrid.
Functions don't have to be pure. We even have classes (functions plus state).

Can we do FP in JavaScript? Yes, the `Array` functions like `map` and `filter` are functional.
The popular [RxJS](https://rxjs.dev/) library is also functional, and if you have worked with it
you have an idea of how complicated functional can get. The big dog in TypeScript functional is the
[fp-ts](https://gcanti.github.io/fp-ts/). Even more than RxJS, it requires massive buy-in
by the entire application team to learn and use this approach.

So am I looking at real FP, or just not using the `class` keyword?

## So... TDD?

I'm buying what TDD is selling. It sounds doable, and individuals can do it even on existing
projects and without forcing every other developer of an application, now and in the future, to
understand and use it. (Indeed, I have started doing this!)

Not sure how to get actually get going? 

Here's the Three Rules of TDD:

1. Write production code only to pass a failing unit test.
2. Write no more of a unit test than sufficient to fail (compilation failures are failures).
3. Write no more production code than necessary to pass the one failing unit test.

Here's some 
[Live Stream Examples doing TDD in JavaScript](https://www.jamesshore.com/v2/projects/lunch-and-learn).

## So... FP?

For me, using TypeScript, no.

[Kent C. Dodd's arguments for a functional approach](https://kentcdodds.com/blog/classes-complexity-and-functional-programming)
is worth a read. I don't buy into the "`this` is too complicated" argument, I think he's just
allergic to to the `class` keyword, but stateless programming with pure functions instead of OOP has
some merit. Not only does it make testing easy, but it plays real nice with using objects defined as
interfaces, instead of classes. One might implement the builder pattern with a class though, for
some logical transformations.

Should we scope functions at the module (file) level instead of class?
Here's how we might do Inversion of Control (IoC) without any fancy Dependency Injection (DI) magic:

```typescript
function a() {
}

function fakeA() {
}

function b() {
}

function fakeB() {
}

export function moduleFactory(
    otherModuleDependency = otherModuleFactory(),
) {
    return {a, b};
}

export function fakeModuleFactory() {
    return {a: fakeA, b: fakeB};
}
```

Much FP code is actually allergic not only to the `class` keyword, but even `function`!
So we might export our factory function as the module default like this:

```typescript
import UserRepository from "user.repository";

export default (repo = UserRepository()) => {
    return {
        a: () => {
        },
        b: () => {
        },
    };
}
```

To me, this is getting less readable than a nice `export class UserLogic` with constructor DI.

[dvlsg on Reddit](https://www.reddit.com/r/typescript/comments/ufucle/comment/i72c5di/?utm_source=share&utm_medium=web2x&context=3)
perhaps sums up the functions (closure) vs class debate best:

> Some people just prefer using closures to using state on classes, and don't like using 'this'.
> That's really it. It's a valid opinion, but it is just an opinion.

Then there's [Dax Raad on Twitter](https://twitter.com/thdxr/status/1510814420691083267?s=20&t=I1fJzUyxQdfZMjA6vG5Dsw):

> a fundamental tradeoff that no one talks about with functional programming is the more you use it,
> the more annoying you become

FP is a big shift. Going all the way with it will make your code unmaintainable by most of the
programming community, and so requires total buy-in. Choosing to do FP in a language it isn't
native to feels even worse than trying convincing folks to switch to a proper FP language.
Arguments can be made to do this, but it is a huge leap.

## So... OOP?

Functional and object-oriented aren't black-and-white polar opposite options.
Let's explore a little.

We can certainly borrow much from FP, just as JavaScript ES2015 did when it introduced
the new `Array` functions like `map` and `reduce`.
Tucking business logic into pure functions and immutable classes adopts some FP concepts
without going too deep.

The function-only and "no mock" (which turns out to just be hand-crafted fakes) approach for DI and
testing just doesn't appear to buy anything. Class constructors are replaced with
factories. [Here is a good example.](https://dev.to/mindplay/a-successful-ioc-pattern-with-functions-in-typescript-2nac)
Whether highly related functions are grouped together as returned by one factory, or grouped
together as one class, doesn't make any difference. I have certainly had trouble with classes
growing large, with all the logic and orchestration for a certain *thing* crammed together, but that
can happen with functions and modules too.

The solution is not to group functions by the thing they operate on, but by dependencies they share
([high cohesion](https://en.wikipedia.org/wiki/Cohesion_(computer_science))) and what they do
([single responsibility](https://en.wikipedia.org/wiki/Single-responsibility_principle)).
Instead of having a `UserService` class that grows huge, have a class per use-case that operates on
a user. That keeps the classes small.

Below I'm using interfaces and a (pure) guard function from `"domain/models"`,
pure functions from `"domain/logic"` and `"utils/assertions"`,
and coordinating it all as a _use case_ in a clean cohesive class.

```typescript
import { isUserSignUpRequest, UserSignUpRequest, UserSignUpResponse } from "domain/models";
import { newUserFactory } from "domain/logic/user";
import { assertValidInput } from "utils/assertions";

// Optionally use DI magic to gain performance of singletons:
// @Injectable()
export class UserSignUpUseCase {
    // Dependencies defaulted - tests can provide mocks or fakes
    constructor(
        auth = new AuthService(),
        userRepo = new UserRepository(),
    ) {
    }

    async process(request: UserSignUpRequest): Promise<UserSignUpResponse> {
        this.validateRequest(request);
        const user = newUserFactory(request);
        await this.userRepo.put(user);
        return {user};
    }

    private validateRequest(request: UserSignUpRequest): void {
        this.auth.assertIsNotAuthenticated();
        assertValidInput(request, isUserSignUpRequest);
    }
}
```

These use-case classes can also be called "Controllers" in the traditional sense.
That name is often used now for RESTful path handling though, so I find `UseCase` is clearer.

Could this be done with module scope exporting a factory function? Absolutely.
Would it be as readable? I say no.

## Rules to Follow?

This is my personal take and how I intend to proceed.
Your research and background may lead you to a different conclusions.

1. Use TDD, with circumstantial flexibility. (Don't be obsessive.)
2. Use types and interfaces for APIs.
3. Favor pure functions for business transformation logic - anything that takes a parameter or two,
   and returns a result or two without mutation or outside state.
4. Use OOP where it has value - but the object must be self-contained so that state changes _only_
   impact that object instance.
5. Use Dependency Injection (DI) with _small_ classes that have a
   [single responsibility](https://en.wikipedia.org/wiki/Single-responsibility_principle) and
   [high cohesion](https://en.wikipedia.org/wiki/Cohesion_(computer_science)).
    * These classes aren't OOP, just collections of functions and dependencies.
    * This can be done with modules and factory functions, but this is more effort for less
      readable syntax than a nice clean class; this syntactic sugar was added for good reason.
      Also, separating the function grouping name (class) from the file name feels more flexible.
6. If a test fake is needed, export it along-side the real code so that it can
   be reused by any test that depend on it.

## File structure

Wrapping all this up, how might I organize the source code?

```plain
.
└── src
    ├── domain
    │   ├── logic  # domain logic functions & object-oriented classes
    │   │   └── user.logic.ts
    │   └── models
    │       ├── index.ts
    │       └── user.ts
    ├── handlers
    │   └── user-sign-up.lambda.ts
    ├── external
    │   ├── dynamodb.service.ts
    │   └── repositories
    │       └── user.repository.ts
    └── use-cases
        └── user
            ├── user-sign-up.test.ts
            └── user-sign-up.ts
```

The entire `domain`, or just `domain/models`, might go into a separate package to be shared with
client code - these models are your API contracts.

The `handlers` are the entry points into your code. You might have separate handlers for different
environments such as one for AWS Lambda and another for a containerized cloud. This puts you on
the road to [Hexagonal Architecture](https://adam.fanello.net/hexagonal-architecture-by-example-meetup).
Here, `user-sign-up.lambda.ts` just deals with unpacking the request from API Gateway and Lambda,
calling the use-case, and formatting the proper response back to Lambda and API Gateway.

The `external` directory is the right-side of your Hexagonal Architecture - the interfaces out
to external sources and targets of state.

Finally, `use-cases` coordinate processing of individual requests and commands.

## Conclusion

These explorations of techniques are fun, but sure do make for long blog posts!
The relation between TDD and FP isn't as strong as I started off thinking, but figuring out how
to make unit testing work with FP was highly relevant.

Let's sum this up:

1. TDD gives you superpowers; embrace it!
2. _Real_ Functional Programming is only practical in a language made for it, 
   but we can take lessons from FP and apply them in other languages.
3. The `class` keyword is not poison, and is useful syntactic sugar beyond OOP.

