Gathering Agility.

TDD Update — My Life is a Hollow Lie

Cover Image for TDD Update — My Life is a Hollow Lie

My Life is Hollow Lie

It’s been more than a year now since I last wrote about my TDD project to rewrite this blog. It’s not that work hasn’t happened — more on that later — it’s rather more that progress has been ridiculously slow. I have a day job that keeps me busy. My time to hone my TDD skills is mostly relegated to weekends. And, as I’m sure you have noticed, lately we have been dealing with this global pandemic that has thrown just about everything out of whack.

Back in July, I had a major panic attack when refactoring. I decided to move my views to a new location. Out of habit, I ran my tests after the move, and was shocked when they all came back true. My server-side testing framework of choice in Javascript is Jest. These tests have some very specific assertions in them, like

expect(result.text).toContain("Hello from your new contact page");

In other words, if the served page does not contain that specific phrase, it should fail. Always. No exceptions. And here, despite that it's pointing to view files that no longer exist in that location, it was passing. For a moment, my life passed in front of my eyes. I teach this crap. I’m reasonably expected to know what I’m talking about, and here I’ve spent several months working on a project that has non-falsifiable tests.

So if you ever feel like you’re struggling with TDD, you’re not alone. Everyone struggles with it. What makes the struggle worthwhile are the results you get when it does work as expected.

I spent a frantic weekend trying to plug up the problems with my test code. Eventually I got the tests to fail as expected. I still have no idea precisely why those tests gave false positives, but it doesn’t matter now. What matters is that my system works the way I expect it to. Not only that, I can prove it’s working as expected.

Goodbye Karma

Looking back, the thing that slowed up my work more than anything else was trying to get my Karma tests up and running. As I mentioned in a previous post, I’ve been following James Shore’s online lessons on using TDD with Javascript, and he uses Karma extensively. He also started coding his system back when Karma still had the unfortunate name Testacular, so some of his lessons are nine years old now.

Karma has some features that are very attractive, like the ability to test Javascript code on any browser whatsoever. For the longest time, I held onto that promise, ignoring newer competitors along the way. Doing this, I discovered the single weakest feature of Karma: namely, that it requires test code to set up its own parallel DOM in order to do any of the client-side testing that is its sole reason to exist.

I have a love-hate relationship to mocks in TDD. OK, it’s a hate relationship. Unless you have a very specific reason to do so, you should avoid mocking altogether. I have seen teams create mocks for API calls, and then create a test to call that mocked API, and then assert on the results that came back from that mock. Every time I see that, I’m like, “Congratulations. Your mocking system works. Too bad about your worthless test though.” The point being that teams will tie themselves into knots creating mocks to isolate stuff that would be better isolated by other means — in the case of the API call, for example, by repacking the returned JSON into domain-specific objects that are testable in isolation.

At their worst, teams will create mocking systems nearly as complex as the system they are mocking. This merely snarls their tests, slows their progress, and probably explains the bad reputation TDD has in some circles. Karma’s architecture is particularly bad in that regard, in that it saddles developers with the crappiest mock ever. In order to use it, developers just need to mock everything on the damn website. This is just a waste of everyone’s time. A UI-side tool like Selenium takes the opposite approach, as it simply calls the web server and probes the resulting DOM directly. That’s a lot cleaner, and puts much less burden on developers.

Worse, calling Karma tests from the command line was a pain in the ass. James Shore does a lot of patching work under the hood to get Karma’s output to pipe back into his build tool, but I was never able to get it to work. If you want a failed test in Karma to stop your build, good luck. Trying to get Karma to do anything useful was death by a million paper cuts and about ten thousand machetes. I’m genuinely surprised anyone still uses it. I have precious little time to develop, and I want to use that time in my problem domain, not wrestling with a tool that has promised, and failed, to make my life easier.

So, after months of investing in Karma, I looked around for alternatives. Fortunately, I didn’t need to look very far, as a team I had been working with raved about Cypress. I had avoided Cypress because at the time it didn’t support any browser other than Chrome. Now, however, it supports Firefox and Edge as well. (I refuse to test any version of IE. It’s time to let that abomination die.)

I ripped Karma out of my system, and had Cypress working on the command line within a few hours. It’s easy to work with, comes with thousands of examples, and has never prevented me from doing what I need to do. I couldn’t be happier with it.

My Tech Stack Thus Far

When I started this project, one of the desired outcomes was to be able to upgrade Node without a whole lot of things breaking in unexpected ways. In that regard, mission accomplished. I have shifted Node versions repeatedly throughout this project, and never had an issue that couldn’t be solved in a few minutes.

I have successfully stayed away from the big JS frameworks like Angular and React, and I frankly have not missed them. In fact, forcing myself to work without them has enabled a level of learning that I would not have had elsewhere.

That’s not to say I don’t use any frameworks. It’s just that the ones I’m using are low level enough that I’m able to have a measure of control over what they’re doing for me.

So here’s what my tech stack looks like:

Function Technology
Server Node, Express
Language Typescript
Server-Side Testing Jest, Supertest
Client-Side Testing Cypress, Chai
Builds JakeJS
Code Quality EsLint, Prettier

And that’s it. Minimal is the name of the game here. There are a few more dependencies in my package.json, but for the most part their function is to support something on this list.

Future Work

Right now, the site is not much. Just three pages. But I am confident that the toolchain I have set up will be able to do everything I can throw at it and more. Even better, if I catch site of a new tool that can make my life better, I can integrate it into my existing code with little to no trouble.

All is not perfect, of course. Here are the areas that I still need to improve on:

  • Cross-platform builds - Right now, I can start and stop the server for my Cypress tests in Mac and Linux environments. Unfortunately, I do a lot of my development work on Windows, which throws a lot of wrenches in the works. I've been running experiments to find a way to start and stop the server reliably on all systems available to me, but it's definitely a work in process.

  • Security - I'd like to get something like OWASP Zap configured in my Jake build so that I can run penetration testing every time I build.

  • Templating - I have been playing around with EJS for my view templates. I may have additional capabilities to explore here, like some lightweight content management, but right now I'm trying to keep it simple.

  • Development Server - I have been contemplating something like NodeMon, so I can see changes to the site immediately without having to rebuild.

  • Publishing to Prod - At some point, I'd like to be able to push my site to my cloud server. That's kind of the entire point of this work.

But for right now, I'm just having fun playing with the technologies. We will see.

Postscript: after writing this, I just had yet another wrestling match with Gatsby, and Gatsby died on me. As a result, I need to rewrite this blog into a new app as soon as possible, and the TDD blog just isn't ready yet.

Gatsby, you are dead to me.