Author Archives: Jason Swett

Factories and fixtures in Rails

One necessity of automated testing is having some data to test with. There are three ways I know of to generate test data in Rails:

  • Manually
  • Fixtures
  • Factories

All three can be viable solutions depending on the situation. Let’s first explore manual creation.

Manual data creation

Manual data creation can be convenient enough if you only have a few attributes on a model and no dependencies. For example, let’s say I have a PaymentType model with just one attribute, name. If I want to test both valid and invalid states, that’s easy:

valid_payment_type = PaymentType.new(name: 'Visa')
invalid_payment_type = PaymentType.new(name: '')

But now let’s say we have the idea of an Order which is made up of multiple LineItems and Payments, each of which has a PaymentType.

order = Order.create!(
  line_items: [
    LineItem.create!(name: 'Electric dog polisher', price_cents: 40000)
  ],
  payments: [
    Payment.create!(
      amount_cents: 40000,
      payment_method: PaymentMethod.create!(name: 'Visa')
    )
  ]
)

That’s annoying. We had to expend mental energy to arbitrarily come up with details (“Electric dog polisher” with a price of $400.00, paid by Visa) that aren’t even necessarily relevant.

What if all we wanted to test was that when the payment total equals the line item total, order.balance_due returns zero?

This is where factories come in handy.

Factories

Factories are actually not a test-specific concept. “Factory method” is a design pattern that you can find in the “Gang of Four” design patterns book. The pattern just happens to come in handy for the purpose of testing.

The idea with a factory is basically that you have a method/function that generates new class instances for you.

In the Gang of Four book they use an example where a factory called Creator will return either an instance of MyProduct or YourProduct depending on certain conditions. So rather than having to say “if this case, then instantiate a MyProduct, otherwise, instantiate a YourProduct” all over the place, you can just say Creator.create(relevant_data) and get back the appropriate class instance.

You might be able to imagine how such a factory would be useful. In the case of testing, the kind of factories we want will be a little bit different. We’re not concerned with abstracting away whether class A or class B gets instantiated. We want to abstract away the generation of irrelevant details and tedious-to-set-up model associations.

Here’s an example of how the setup for an Order instance might look if we used a factory, specifically Factory Bot. (By the way, Factory Bot is the most popular Rails testing factory but there’s nothing particularly special about the way it works. The ideas in Factory Bot could have been implemented any number of ways.)

order = FactoryBot.create(
  :order,
  line_items: [FactoryBot.create(:line_item, price_cents: 40000)],
  payments: [FactoryBot.create(:payment, amount_cents: 40000)]
)

In this case we’re specifying only the details that are relevant to the test. We don’t care about the line item name or the payment method. As long as we have a payment total that matches the line item total, that’s all where care about.

How would we achieve this same thing using fixtures?

Fixtures

First off let me say that I’m much less experienced with fixtures than I am with factories. If you want to learn more about fixtures I suggest you read something from someone who knows what they’re talking about.

Having said that, if we wanted to set up the same test data that we did above using factories, here’s how I believe we’d do it with a fixture. Typically fixtures are expressed in terms of YAML files.

# orders.yml
payments_equal_line_item_total:
  # no attributes needed

# line_items.yml
electric_dog_polisher:
  order: payments_equal_line_item_total
  name: 'Electric dog polisher'
  price_cents: 40000

# payment_methods.yml
visa:
  name: 'Visa'

# payments.yml
first:
  order: payments_equal_line_item_total
  payment_method: visa
  amount_cents: 40000

Once the fixture data is established, instantiating an object that uses the data is as simple as referring to the key for that piece of data:

order = orders(:payments_equal_line_item_total)

So, what’s best? Factories, fixtures, or manual creation?

Which is best?

I think I’ve demonstrated that manually generating test data can quickly become prohibitively tedious. (You can often mitigate this problem, though, by using loose coupling and dependency injection in your project.) But just because manual data generation doesn’t work out great in all scenarios doesn’t mean it’s never useful. I still use manual data generation when I only need to spin up something small. It has a benefit of clarity and low overhead.

Like I said above, I’ve definitely used factories more than fixtures. My main reason is that when I use factories, the place where I specify the test data and the place where I use the test data are close to each other. With fixtures, on the other hand, the setup is hidden. If I want to see exactly what test data is being generated, I have to go into the YAML files and check. I find this too tedious.

Another issue I have with fixtures in practice is that, in my experience, teams will set up a “world of data” and then use that big and complicated world as the basis for all the tests. I prefer to start each test with a clean slate, to the extent possible, and generate for each test the bare minimum data that’s needed for that test. I find that this makes the tests easier to understand. I want to be clear that I think that’s a usage problem, though, not an inherent flaw in the concept of fixtures.

There’s also no reason why you couldn’t use both factories and fixtures in a project. I’m not sure what the use case might be but I could imagine a case where fixtures provide some minimal baseline of fixed data (the payment types of Visa, MasterCard, cash, etc. come to mind as something that would be often-needed and never changed) and then factories take the baton from there and generate dynamic data on a per-test basis.

My go-to test generation method is factories, and that’s what I generally recommend to others. But if the use case were right I wouldn’t hesitate to use fixtures instead of or in addition to factories.

Why “users don’t care about code” is a bad saying

I’ve heard it said a lot of times that “users don’t care about code“. Along with things like “perfect is the enemy of the good”, this saying falls under the category of technically true, but unhelpful.

In order to understand why this saying is harmful, let’s dissect it. When people say “users don’t care about code”, what’s the meaning behind it? Here’s what I think they’re trying to communicate:

Jason, don’t waste time perfecting the code, which users won’t ever see. Users don’t care how clean or modular or whatever your code is. User only care that the product does what they need it to do. So please, be reasonable. Focus on the part of the work that really matters.

On the surface this appeal seems totally reasonable. Stop polishing that code for the 15th time, Jason! We need to move, we need to ship!

But like I’ve said elsewhere, the general danger in coding is not that developers spend way too much time gold-plating the code. A far bigger problem in the industry is that developers rush their work and do a shit job that paints them tighter and tighter into a corner until they literally aren’t able to ship features anymore.

I’ve seen teams get close to this point. In 2016-2017 I worked with a team that theoretically released to production once a month. However, their codebase was so fragile that every time new code was written, the new code broke existing features. Plus the new code itself usually didn’t work right. QA would find bugs and the development team would have to revisit their features. So the release would get delayed. And delayed. And delayed. Frustration would mount. Finally the release would go out, two weeks late, and now the dev team would be under increasing pressure to deliver the next chunk of work in less time. So what would the developers do? Same thing everyone always does, which is cut corners to get the job done on schedule. This would of course further exacerbate the problem and the next release would take even longer.

What caused these delayed releases and fragile features? Bad code. Did the users care about the delayed releases and fragile features? Yes, these issues had an acute negative impact on users and all throughout the organization I was working for.

So yes, it’s true that users don’t literally care about actual code. But when your code is so shitty that your product is full of bugs and you can’t get a release out to save your life, users care about that.

What this has to do with testing

If a developer or manager believes that users don’t care about code, he or she probably also believes that users don’t care about test coverage.

And in the same sense that it’s technically true that users don’t care about code, it is technically true that users don’t care about tests.

When a dev team is under pressure to release a feature, what’s usually the first thing to go? Tests, of course. I can’t count how many times I’ve heard somebody say something like, “We would have written tests but we wanted to get something out quickly.” It’s commonly believed that skipping tests saves time but except for the most trivial features it’s usually a lot more time-consuming to test features manually. Like every other form of cutting corners, skipping tests creates technical debt and makes our work harder.

Cutting corners and consciously incurring technical debt doesn’t just make our work harder at some abstract distant time in the future. Technical debt costs technical interest. Those interest payments typically start being due immediately. And the more technical debt we have, the slower and less reliably we’re able to deliver value to users.

So please don’t tell developers that users don’t care about code as a way to encourage “speed”. Poorly-written code does affect users.

“Perfect is the enemy of the good” is a false dichotomy

“Perfect is the enemy of the good” is a very true and helpful saying that’s often applied to creative work.

For example, if I fret over the wording of a blog post and hesitate to hit publish because my writing isn’t perfect, then I’ve prevented myself from releasing value into the world. Better to just publish something that’s pretty good and get it out there.

But “perfect is the enemy of the good” is an egregiously counterproductive mindset in the context of software projects.

The reason is that it’s a false dichotomy. Software projects are always behind. All development teams are always under extreme time pressure. Practically every production codebase in existence is a crappy legacy codebase. So the normal day-to-day state of affairs for most teams is that they’re cutting corners and doing work they’re not proud of in order to maintain some semblance of productivity. (This is not a good way to work but it’s the common reality.)

When leadership comes to a dev team and asks them to perform a miracle, the choice the dev team is faced with is not “should we make it good or should we make it perfect?” Rather, the choice is between: a) business as usual, i.e. piling yet more shitty code on top of the existing pile of shit, or b) switching to “crisis mode” and committing whatever crimes and sins and atrocities are necessary in order to meet the desired timeline. The dilemma is not perfect vs. good but crappy vs. ghastly.

Yes, it’s of course important to actually ship features and meet commitments. But the way to ship features is to employ healthy engineering practices and to take on realistic scopes of work, not to do work of intentionally low quality.

What exactly makes “bad” code bad?

As programmers we often talk about “bad” code. But interestingly, I don’t believe I’ve ever heard someone actually define exactly what bad code is.

I find this unfortunate because saying code is “bad” is actually meaningless, especially to a non-technical manager. If a non-technical person hears me say that our code is “bad”, they might believe me, but they won’t understand what it means for the code to be bad or what it would take to make the code good.

If we can put our finger on exactly what we mean when we say “bad code”, maybe it will help us go from sounding like we’re complaining about the code to getting on the path to making the bad code good.

So I’m going to try to define what bad code means.

My definition of bad code

Bad code is code that is risky and/or expensive to change.

Let me break that down.

What does it mean for code to be risky to change?

Have you ever been scared to deploy a code change to production? I have.

What exactly are we afraid of when we’re afraid to deploy a change? Basically, we’re afraid of introducing a regression. If the code we’re working on is hard to understand, then that means we don’t understand the implications of our changes. That means that all the carefulness in the world can’t protect against accidentally breaking some part of the application when we make a change.

Consequently, we cause problems for customers. We cause customers to lose money. We make customers frustrated with our employer’s product. We make everyone trust the development team less. These are all genuinely bad things. If a codebase is sufficiently hard to understand that it poses these risks to the development team, then I would say that that code is bad.

What does it mean for code to be expensive to change?

The cliche that time is money is true. The longer it takes a developer to be able to understand a system, the more expensive that system is to maintain.

In addition to “bad code” another term that gets thrown around to the point of meaninglessness is “maintainability”. We often talk about code being “maintainable” or “unmaintainable” but rarely talk about what exactly that means. Another way to say the same thing would be to say that code has a low cost of maintenance or high cost of maintenance.

This is true of not just code but all parts of a system. If we use unclear variable names, convoluted database schemas, or methods and classes that are too big to quickly and easily grasp, then future maintainers of our system will have to spend more time (which means more money) to be able to understand the system.

The reason I’m talking about understandability is because in order to confidently change code, you first have to understand it to some extent. Have you ever made a one-line change that took literally three seconds to make but then spent 30 minutes studying the code and testing your work because you weren’t sure you understood the implications of your change? The longer that “studying and testing” period, the more expensive the code is to maintain.

What a “walking skeleton” is and why I always start projects with one

Every software project involves some amount of mystery and uncertainty. In fact, it’s often the case that a software project is hardly made of anything but mystery and uncertainty.

Some jobs are more mysterious than others. I was recently asked by a client to build a Rails application that contained CRUD interfaces for a Patient resource and an InsuranceType resource. That work was not very mysterious.

My client also asked me to set up staging and production environments for this application on AWS. This might sound easy but setting up a Rails application on AWS/Elastic Beanstalk is totally not straightforward, even though it’s 2018, and even though I myself wrote guides on how to get Rails working on AWS. The work of getting the application deployed to AWS was in fact mysterious.

Enter the Walking Skeleton

A Walking Skeleton is basically the thinnest possible slice of all the layers in an application. The idea is that you front-load the difficult/mysterious work in a project so you get it out of the way early while the stress level is low and the remaining budget is high.

I learned about the idea of a Walking Skeleton from Growing Object-Oriented Software, Guided by Tests. The name refers to the idea that your early application hardly does anything but it can still stand up.

The authors’ suggestion as I understood it was to set up both an application’s production environment and testing infrastructure as part of a Walking Skeleton, both of which can be mysterious/time-consuming/difficult to set up.

So in the case of my application, I might get my Elastic Beanstalk environment set up, create just one of my CRUD interfaces (whatever the simplest one is) and write a couple feature specs to exercise my CRUD interface. This will force me to figure out how to get AWS/EB set up (again, non-trivial) as well as install RSpec, Factory Bot, Capybara, etc. From that point on everything is just incremental.

The alternative to a Walking Skeleton

Failure to create a Walking Skeleton can contribute to ghastly project failure. If you’d like to hear one of my horror stories about this you can check out another article of mine, Deploy to Production on Day One.

How dependency injection can make Rails tests easier

“Dependency injection” is a fancy-sounding term. When I first heard it I assumed it referred to some super-advanced technique. It wasn’t until years later that I realized that dependency injection is a pretty straightforward technique once you understand what it is.

My aim with this post is to cut through the jargon and show you in simple terms what dependency injection is and why it’s useful.

But first: why are we interested in this topic?

Why bother learning dependency injection?

Depending on how it’s written, some code can be easy to test and some code can be hard to test. Code with entangled dependencies is hard to test.

Why is code with entangled dependencies hard to test? Imagine I have a class `Order` that requires instances of a class called `Payment` in order to function. Let’s then imagine that `Payment` needs some `PaymentType` instances (Visa, MasterCard, cash, etc.) in order to work.

This means that in order to test the class I’m interested in, `Order`, I have to bring two other classes into the picture, `Payment` and `PaymentType`, just to perform the test. And what if `Payment` and `PaymentType` in turn depend on other classes? This test is going to potentially be very tedious to set up.

The opposite of having entangled dependencies is having loose coupling and modularity. Modular, loosely coupled code is easy to test. A number of factors have a bearing on how modular and loosely coupled your code will end up. What I want to show you right now is how dependency injection can help make your code more modular and therefore more easily testable.

An dependency-laden Rails model

Let’s say you’re working on a legacy project that you recently inherited. There’s very little test coverage. You encounter an ActiveRecord model called `CustomerFile`. There’s a method called `parse` that evidently parses a CSV.

class CustomerFile < ActiveRecord::Base
  belongs_to :customer

  def parse
    rows = []

    content = File.read(customer.csv_filename)
    CSV.parse(content, headers: true) do |data|
      rows << data.to_h
    end

    rows
  end
end

Let’s focus on this line for a second: `content = File.read(customer.csv_filename)`.

Apparently a `CustomerFile` object has an associated `customer` object which in turn has a `csv_filename`. How exactly does `customer` get set? It’s not clear. Where exactly is the file that `customer.csv_filename` points to? That’s not obvious either.

We can try to write a test for `CustomerFile` but it probably won’t go very well.

RSpec.describe CustomerFile do
  describe '#parse' do
    let(:customer_file) { CustomerFile.new }

    it 'parses a CSV' do
      # How do we know what to expect?
      # Where is the file that "customer.csv_filename" refers to?
      # expected_first_row = ?

      expect(customer_file.parse[0]).to eq(expected_first_row)
    end
  end
end

Our attempt to write a test hasn’t proven very fruitful. The challenge of writing a test for this class is somewhat “uncomeatable”.

The reason it’s hard to write this test is that `CustomerFile` has a dependency inside of a dependency. We don’t know how to make a `customer`, and even more problematic, we don’t know how to make a CSV file for that customer.

Applying dependency injection for easier testability

Let’s imagine now that `parse` doesn’t require that we have a `customer` with `csv_filename` that points to some mysterious file on the filesystem somewhere.

Let’s imagine a version of `parse` that just takes the file contents as an argument.

class CustomerFile < ActiveRecord::Base
  belongs_to :customer

  def parse(content)
    rows = []

    CSV.parse(content, headers: true) do |data|
      rows << data.to_h
    end

    rows
  end
end

When we try to write a test now, we’ll see that it’s much easier.

RSpec.describe CustomerFile do
  describe '#parse' do
    let(:customer_file) { CustomerFile.new }
    let(:content) { "First Name,Last Name\nJohn,Smith" }

    it 'parses a CSV' do
      expected_first_row = {
        'First Name' => 'John',
        'Last Name' => 'Smith'
      }

      expect(customer_file.parse(content)[0]).to eq(expected_first_row)
    end
  end
end

In this case `parse` doesn’t know or care where the CSV content comes from. This means that we don’t have to bring the filesystem into the picture at all which makes writing this test very convenient. No `customer` object or `customer.csv_filename` value necessary.

If we want to use `parse` for real in the application we can just pass in the file contents like this: `parse(File.read(customer.csv_filename))`.

Conclusion

Modular, loosely coupled code is testable code. You can use dependency injection to help make your code more modular.

Atomic commits and testing

What atomic commits are and why they’re advantageous

I like to make Git commits very frequently. Looking at the Git log for a recent project, it looks like I tend to commit about every 5-15 minutes.

I find that the smaller my commits are the easier I make life for myself. I remember painful occasions in the past where I would do a big chunk of work, maybe two hours or so. Things would be going fine, going fine, and then all the sudden my work would collapse on itself and I wouldn’t be able to figure out how to get it back to a working state.

At that point my options would be either to scratch my head for the next hour to try to figure out what went wrong or to revert the whole two hours’ worth of work. I had painted myself into a corner. When I teach programming classes I see students paint themselves into a corner like this all the time.

This kind of stuff doesn’t really happen to me anymore. One of the main reasons is that I practice atomic commits.

“Atomic commit” is basically a fancy way of saying a commit that commits one and only one thing. It’s a single complete unit of work.

Students of mine often find it funny that I’ll make a commit after changing just one or two lines of code. But a commit is not just something I do after completing a piece of work. A commit is something I do before I start a piece of work.

Let’s say I have Chunk A and Chunk B, two unrelated pieces of work. Chunk A is a 30-second change. Chunk B is a 20-minute change. A lot of people might not bother committing Chunk A because it’s so small. But then Chunk A “comes along for the ride” when I’m working on Chunk B. If I screw up Chunk B and have to bail and revert and start over, then I also end up reverting Chunk A. Or if a week later I find out Chunk B introduced a bug and I need to revert Chunk B at that point, Chunk A gets reverted as well even though it has nothing to do with Chunk B.

These are the things I have in mind when I commit what might seem to others like a comically tiny change.

Atomic commits also make it easier to track down the source of mysterious regressions. Let’s say a feature was known to be working on January 5th and broken on June 11th and nobody knows when exactly the feature broke. Git bisect can make it very quick and easy to find out exactly which commit introduced the regression. At least, it’s easy if the team has been practicing atomic commits. If each commit is huge and contains multiple pieces of work, git bisect loses a lot of its usefulness.

By the way, I think “WIP” (work in progress) is one of the worst possible commit messages. First, it basically screams “This commit is not a complete unit of work.” Second, it’s about as vague as it gets. Committing “WIP” is basically saying “Rather than take 30 seconds to think of a meaningful commit message, I’d rather make you take several minutes to try to figure out what this commit is all about.” Please don’t commit “WIP”.

What atomic commits have to do with testing

I find it advantageous to make sure each commit leaves the application’s test suite in a passing state.

This way you’re free to check out any commit in the application’s history and you can have a reasonable expectation that the test suite will pass. (This becomes less true the further back in history you go, of course, but it’s at least true for the recent commit history which is usually the history you’re most interested in.)

Some developers don’t find it important to keep the test suite passing on every commit. When this is the case I might check out a certain revision and see that the tests don’t pass. Then I’m forced to wonder: is it supposed to be like this or was this an accident? There’s nothing more frustrating than getting tripped up by a failing test, finally asking someone about it, and getting the response, “Oh, yeah, that test is supposed to fail.” Allowing failing tests to be committed to the repo arguably defeats the purpose of having tests.

It’s okay for a commit not to add a test. It’s okay for a commit to add a passing test. But if a commit commits a failing test then the commit is not a complete unit of work, and the benefits of a repo full of atomic commits are to some extent lost.

I also often find it handy to use git bisect in conjunction with an application’s test suite. Using git bisect is all about going back to a certain point in time and asking “Does feature X work at this commit?” Sometimes the test that answers that question is a manual check. Sometimes it’s an automated test. If the team has a habit of making only small, atomic commits, using git bisect together with tests is a lot easier.

Reverse job ad

.footer-opt-in {display: none;}

I’m looking for a new job. Inspired by this reverse job ad, I decided to create one of my own.

Who I am

I’m Jason Swett, software engineer. I’ve been coding since the ’90s. I’ve taught programming in Nigeria, Bulgaria, the Netherlands and even Missouri. I’m the host of the Ruby Testing Podcast and author of Angular for Rails Developers. Most of my work over the last six years has been in Ruby on Rails. I’m primarily a back-end engineer although I’ve done my fair share of JavaScript work as well.

What I’m looking for in my next role

In my next role I can see myself doing any combination of the following things:

  • Training and mentoring junior developers
  • Developing and documenting the organization’s processes (e.g. incident response process)
  • Helping foster a healthy relationship between engineering and other parts of the organization
  • Helping engineering follow Agile development methodologies (pragmatically, not dogmatically)
  • Helping engineering follow best practices like TDD, continuous integration and continuous delivery
  • Mopping the floor

Why I’m looking for a new job

While I’m quite happy at the job I’m working now, there are two logistical problems. First, I live in Eastern time, but the company is located on the west coast, and I often have to work Pacific hours. Second, I’m one of the only remote people at the company which I find a little bit isolating.

So in my next role I’m looking for something that will allow me to work remotely from Michigan. I’d prefer to work for a remote-first company if possible. (This is not a hard requirement though.)

I would also prefer to work for a product company as opposed to a development agency. I like to be able to work continuously on one single thing for a long period of time as opposed to shipping a project for a client and never seeing it again.

Want to talk?

If you’d like to talk about working together, my email address is jason@codewithjason.com.

Deploy to production on day one

Sleeping at the office, nothing to show

You know a project is under serious pressure when people start camping out at the office.

In 2015 I got a job at an agency where they were doing a big project for a large Chinese electronics manufacturing company. The project had been underway for about six months.

Two of the developers, who I’ll call Dave and Eric, had been working on the iOS and Android app portion of the project. I was told that for a time they had been sleeping at the office at night in an effort to hit their timelines.

The application had a Rails backend. I have a vague memory that the person who wrote most of the Rails app wasn’t around anymore.

Six months into the project, with a good portion of the budget already spent, the agency had no working application to show their client.

There were a lot of problems with the way the project had been carried out up to the point I got involved. They gave the design work to a very junior designer. The designs didn’t make sense and were not implementable. In the apps that were built, many things were only partway done and very little was all the way done.

There was also no production environment. For whatever reason it was decided that the project would be hosted on AWS. I had never touched AWS at the time but simply because I was the least unqualified person on staff to do it, the task of getting the Rails app onto AWS fell to me.

It took me forever to get things working on AWS. But I finally got it. It was not a simple deployment. The nature of the application required servers in different regions of the world. Because AWS regions are independent, we had to devise a complex messaging system to keep data in sync across multiple regions. It was a nightmare. Compounding the nightmare was the fact that we were under intense time pressure. Everything we were doing was supposed to have been done weeks or months prior.

Done by Thanksgiving? “I think we can do it!”

One of my most vivid memories from this project was kind of a “go/no go” meeting with a group of high-up people at the agency. This meeting included myself, the agency president, several people with “VP” in their title (which was almost everybody at the agency) and a few others. There were maybe about 12 people in the room. At this point in time we had very little working in production and about six weeks before “pencils down” time. If we didn’t have the project done by Thanksgiving, the whole thing was for naught.

At this meeting we discussed what still needed to be done relative to the amount of time we had left. The scope was not very negotiable. My guess was that in order to get everything done in our scope we would need about six months. I remember that we went around the table at this meeting and had each person say whether they thought we could or could not hit our Thanksgiving deadline. “I think we can do it,” the first person said. “I think we can do it!” the next person said. Every single person in the meeting said they thought we could do it. Well I didn’t think we could do it. So I said, “No, I don’t think we can do it.” I could tell everybody was annoyed with me and thought I was being a stick in the mud. Six months of work when we only had six weeks left seemed “too bad to be true”. I agreed it sucked that we couldn’t get six months’ work of work done in six weeks. But the amount that it would suck to not get it done had no bearing on whether we could actually get it done.

$300,000+ down the drain

Turns out I wasn’t crazy. We got nowhere near getting the project done in time. The client spent (as far as I understand) multiple hundreds of thousands of dollars and got nothing.

How this sad story could have been avoided

Not every part of every software project is equally challenging. Some tasks, like creating a new CRUD interface in a Rails project, are really familiar and pretty trivial to implement. Others, like setting up an AWS environment across different regions and setting up a message queuing system so they can talk to each other, when you’ve never touched AWS in your life, are not so trivial.

When you have a mix of mysterious and non-mysterious work in a project you should do the most mysterious work first. Don’t save the super hard stuff for last.

You might think this is common sense. I would think so too. But surprisingly many people leave the most mysterious part of a project for the 11th hour.

This agency’s failed project might not have been quite such a spectacular failure if they had deployed to production on day one. If you deploy a mostly-empty application to production on day one, there’s way less stuff to have to get working. If something breaks in the process of setting up the production environment, you have way less stuff to sort through to figure out exactly where the problem lies. The stress level is also much lower at the beginning of a project.

Getting a production environment in place on day one makes it so every subsequent deployment is a non-event. Throughout the project you can do a number of deployments every week. If the project deadline is Thanksgiving there need not be a mad scramble to get things set up and working the day before Thanksgiving. You just deploy like normal. The workload might be heavier but the deployment process is uneventful.

For less risk and less stress, deploy to production on day one.

What does all this have to do with automated testing?

Like the authors of GOOS say, a healthy development process involves feedback loops ranging from seconds to months. These kinds of feedback loops may include:

  • Unit tests (seconds)
  • Entire unit + integration test suites (minutes)
  • Pair programming sessions (seconds to minutes)
  • PR reviews (minutes to hours)
  • Feature releases (days to weeks)
  • Entire development projects (weeks to months)

These feedback loops are enabled by practices like automated testing, pair programming, continuous integration and continuous delivery/deployment. These tests don’t exist in isolation but weave and synergize with each other. It’s hard to practice continuous deployment when you don’t have any tests. Continuous integration shines a light on your tests and forces you to pay attention to how healthy your test suite is. In addition to deploying to production on day one, it’s immensely helpful to get your testing infrastructure in place on day one and release a tiny feature complete with tests.

How I write model tests

I recently read a post on /r/ruby that asked for RSpec model testing best practices. I gave an answer, but also wanted to give some concrete examples, so here we go.

What follows is how I’d write a spec for a new model. I actually took a model from an existing application I wrote and just blew away the code so I could start over from scratch.

Once I got a ways into this post I realized that it was getting pretty long and I still hadn’t gotten very far into the “meat” of how I’d write a model spec. So I intend to write a follow-up post that goes more in-depth.

Starting point

Here’s what my `Restaurant` model and test look like when they’re empty.

class Restaurant < ApplicationRecord
end
require 'rails_helper'

RSpec.describe Restaurant, type: :model do
end

And just so you can see what the `Restaurant` class’s attributes are, here’s a snippet of `db/schema.rb`. Most of these attributes won’t come into the picture. We’ll mostly just deal with `name` and `phone`.

create_table "restaurants", force: :cascade do |t|
  t.string "name", null: false
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.string "phone", null: false
  t.integer "business_model_id", null: false
  t.index ["business_model_id"], name: "index_restaurants_on_business_model_id"
  t.index ["name"], name: "index_restaurants_on_name", unique: true
  t.index ["phone"], name: "index_restaurants_on_phone", unique: true
end

The first spec

To me the most natural thing to test is the presence of the restaurant name. I’ll write a failing test for presence of name using Should Matchers.

require 'rails_helper'

RSpec.describe Restaurant, type: :model do
  it { is_expected.to validate_presence_of(:name) }
end

When I run this spec, it fails, as I expect.

rspec spec/models/restaurant_spec.rb    
F                                  

Failures:                          

  1) Restaurant should validate that :name cannot be empty/falsy       
     Failure/Error: it { is_expected.to validate_presence_of(:name) }  
                                   
       Restaurant did not properly validate that :name cannot be empty/falsy.                                                                  
         After setting :name to ‹nil›, the matcher expected the Restaurant to                                                                  
         be invalid, but it was valid instead.                         
     # ./spec/models/restaurant_spec.rb:4:in `block (2 levels) in <top (required)>'                                                            

Finished in 0.29263 seconds (files took 1.34 seconds to load)          
1 example, 1 failure               

Failed examples:                   

rspec ./spec/models/restaurant_spec.rb:4 # Restaurant should validate that :name cannot be empty/falsy

I add the name validator to make the spec pass.

class Restaurant < ApplicationRecord
  validates :name, presence: true
end

And indeed, the spec now passes.

rspec spec/models/restaurant_spec.rb
.

Finished in 0.2793 seconds (files took 1.32 seconds to load)
1 example, 0 failures

Spec for phone presence

Now that name presence is taken care of I turn my attention to phone. The spec in this case is the exact same.

require 'rails_helper'

RSpec.describe Restaurant, type: :model do
  it { is_expected.to validate_presence_of(:name) }
  it { is_expected.to validate_presence_of(:phone) }
end

I run the spec and it fails. I then add a presence validator:

class Restaurant < ApplicationRecord
  belongs_to :business_model
  validates :name, presence: true
  validates :phone, presence: true
end

The spec passes now.

Phone number format validity

Unlike restaurant name, which could be pretty much anything, the phone number has to have a valid format. For example, “123” of course isn’t a valid phone number. I add a failing test for this case. I’m actually not sure what I expect the error message to be, so I just put “invalid format”. After I run the spec I can update the error message in my test to match the actual error message.

require 'rails_helper'

RSpec.describe Restaurant, type: :model do
  it { is_expected.to validate_presence_of(:name) }
  it { is_expected.to validate_presence_of(:phone) }

  describe 'when phone number is too short' do
    it 'is not valid' do
      restaurant = build(:restaurant, phone: '123')
      restaurant.valid?
      expect(restaurant.errors[:phone]).to include('invalid format')
    end
  end
end

As I expect, this test fails.

rspec spec/models/restaurant_spec.rb    
..F                                

Failures:                          

  1) Restaurant when phone number is too short is not valid            
     Failure/Error: expect(restaurant.errors[:phone]).to include('invalid format')                                                             
       expected [] to include "invalid format"                         
     # ./spec/models/restaurant_spec.rb:11:in `block (3 levels) in <top (required)>'                                                           

Finished in 0.33318 seconds (files took 1.31 seconds to load)          
3 examples, 1 failure              

Failed examples:                   

rspec ./spec/models/restaurant_spec.rb:8 # Restaurant when phone number is too short is not valid

Now I add a format validator using a regex I found on the internet.

class Restaurant < ApplicationRecord
  belongs_to :business_model
  validates :name, presence: true
  validates :phone, presence: true, format: {
    with: /\A(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}\z/
  }
end

The spec now fails because the expected error message, “invalid format” doesn’t match the actual error message, “is invalid”.

rspec spec/models/restaurant_spec.rb
..F

Failures:

  1) Restaurant when phone number is too short is not valid
     Failure/Error: expect(restaurant.errors[:phone]).to include('invalid format')
       expected ["is invalid"] to include "invalid format"
     # ./spec/models/restaurant_spec.rb:11:in `block (3 levels) in <top (required)>'

Finished in 0.40668 seconds (files took 2.03 seconds to load)
3 examples, 1 failure

Failed examples:

rspec ./spec/models/restaurant_spec.rb:8 # Restaurant when phone number is too short is not valid

So I update my expected error message.

require 'rails_helper'

RSpec.describe Restaurant, type: :model do
  it { is_expected.to validate_presence_of(:name) }
  it { is_expected.to validate_presence_of(:phone) }

  describe 'when phone number is too short' do
    it 'is not valid' do
      restaurant = build(:restaurant, phone: '123')
      restaurant.valid?
      expect(restaurant.errors[:phone]).to include('is invalid')
    end
  end
end

And now the spec passes.

rspec spec/models/restaurant_spec.rb    
...                                

Finished in 0.38825 seconds (files took 2.03 seconds to load)          
3 examples, 0 failures         

Tests for other phone cases

By adding this phone regex I’ve actually broken a rule of TDD: only write enough code to make the test pass. If you only write enough code to make the test pass, you know that all the code you’ve written is covered by tests.

In this case I “wrote some code” (copy/pasted a regex from Stack Overflow) that didn’t have tests. So I’m going to go back and add more test cases. One case will say, “when the phone number is valid, the restaurant object should be valid”. The other will say “when the phone number is all numbers, the restaurant object should not be valid”.

require 'rails_helper'

RSpec.describe Restaurant, type: :model do
  it { is_expected.to validate_presence_of(:name) }
  it { is_expected.to validate_presence_of(:phone) }

  describe 'phone' do
    describe 'when phone number is valid' do
      it 'is valid' do
        restaurant = build(:restaurant, phone: '(555) 555-5555')
        expect(restaurant).to be_valid
      end
    end

    describe 'when phone number is too short' do
      it 'is not valid' do
        restaurant = build(:restaurant, phone: '123')
        restaurant.valid?
        expect(restaurant.errors[:phone]).to include('is invalid')
      end
    end

    describe 'when phone number is all letters' do
      it 'is not valid' do
        restaurant = build(:restaurant, phone: '(AAA) AAA-AAAA')
        restaurant.valid?
        expect(restaurant.errors[:phone]).to include('is invalid')
      end
    end
  end
end

These tests all pass. There’s a little duplication in my test, though, so I’m going to refactor.

require 'rails_helper'

RSpec.describe Restaurant, type: :model do
  it { is_expected.to validate_presence_of(:name) }
  it { is_expected.to validate_presence_of(:phone) }

  describe 'phone' do
    let(:restaurant) { build(:restaurant) }

    describe 'when phone number is valid' do
      it 'is valid' do
        restaurant.phone = '(555) 555-5555'
        expect(restaurant).to be_valid
      end
    end

    describe 'when phone number is too short' do
      it 'is not valid' do
        restaurant.phone = '123'
        restaurant.valid?
        expect(restaurant.errors[:phone]).to include('is invalid')
      end
    end

    describe 'when phone number is all letters' do
      it 'is not valid' do
        restaurant.phone = '(AAA) AAA-AAAA'
        restaurant.valid?
        expect(restaurant.errors[:phone]).to include('is invalid')
      end
    end
  end
end

All the tests still pass.

rspec spec/models/restaurant_spec.rb    
.....                              

Finished in 0.30868 seconds (files took 1.37 seconds to load)          
5 examples, 0 failures

To be continued

What I’ve written in the post is representative of the start of what I’d put in a model test but it’s certainly not the whole thing. What if my model contains some non-trivial methods? This post is already getting kind of long, so I plan to continue this in a Part 2.