After you finish the Rails tutorials and start your own app, things get confusing. Like where does your non-CRUD, general logic go? How does grabbing followers from Twitter fit into MVC? Ask two people, and you get four answers. Or your thread devolves into a bunch of smart people insulting each other for hours, while you beat your head against the desk. Either way, you’re left with a headache.
You can’t build the app you’ve been dreaming of without some general, non-Rails logic. So where do you put your code, and still keep things simple?
The easy place to start
When I have logic that feels related to an existing ActiveRecord model, I’ll start by putting it into that model. For example, if I had a
Game model and I wanted to import a bunch of games from CSV files, I’d put that method right onto the
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
You have all the information your methods need right at hand. It’s easily testable. And it’s probably where a new contributor would look for that logic first.
But if you keep adding and changing that model, it’ll get big and complicated. Different parts of the model will interact in strange ways. The more you change it, the harder it will be to change.
In that case, you’d probably want to refactor that code out to a non-ActiveRecord model.
Just because it’s in a Rails app, doesn’t mean it has to inherit from Active/Action-whatever.
You can write your own Ruby code, in plain Ruby objects, and use them in your Rails app. These objects can still be called models, because they’re modeling part of your problem. They just don’t have an ActiveRecord database storing their data.
The next time I worked on that game CSV parser, the
Game class was getting a little too big. So I moved the parser logic into its own
The whole commit is here, but this is what the non-ActiveRecord class looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
I’ll go right to creating a new plain Ruby object if the logic I’m adding doesn’t feel related to any specific ActiveRecord model. Or if the code seems like it should be a part of a thing that doesn’t exist yet in the app. Otherwise, they mostly pop up through refactoring.
With plain Ruby objects, you can write anything. But knowing you can write anything doesn’t help you with direction. What methods do you need? How will all your new objects interact?
A lot of Rails apps use the same categories of plain Ruby objects. These categories are patterns you can follow to write code that other developers would recognize. You might have heard of a few of them already.
Service objects, presenters, and jobs
There’s nothing special about service objects, presenters, and jobs. They’re just plain Ruby objects that act in a particular recognizable way.
For example, a Resque job is a plain Ruby class that has a
perform method and a
1 2 3 4 5 6 7 8
perform is called when the job is run.
A presenter is a plain Ruby object with code that only makes sense inside a view:
1 2 3 4 5
It might also include Rails’ view helpers, or take a few different objects and treat them as one unified object for the view’s convenience.
A service object is a plain Ruby object that represents a process you want to perform. For example, writing a comment on a post could:
- Leave the comment.
- Send a notification mail to the author of the post.
A service object could do both, and keep that logic out of your controller.
There’s a great take on service objects here. It’s full of examples.
For simple processes, I don’t bother with service objects. But if the controller starts getting too heavy, they’re a good place to put that extra logic.
You can use these patterns to organize your own business logic. They’re just plain Ruby objects, but they’re Ruby objects that share a certain flavor, that have a name, and that you can talk to other developers about.
Where do you start?
There are a lot of different places your non-Rails business logic could go. It might be hard to choose. So here’s what I do:
- If the logic is mostly related to an existing class, even if it’s an ActiveRecord model, I put it in that class.
- If it doesn’t fit an existing class, I create a new plain Ruby class to hold the logic.
- If it feels like the logic should be part of something that doesn’t exist yet, I create a new plain Ruby class for it.
- If I come back to the code later, and the model is getting too complicated, or the code doesn’t make sense in that model anymore, I’ll refactor it into its own plain Ruby class.
- If the code only makes sense in a view, I’ll add it to a helper or create a presenter.
- If the code doesn’t need to run during an HTTP request, or has to run in the background, it goes in a job.
- If I’m juggling several different models or stages of a process, and it’s making the controller too hard to understand, I’ll put it into a service object.
How about you? Where does your code go? And do you have any patterns besides these that you’ve found helpful? Leave a comment and let me know.
And if you don’t have a process yet, try mine. See how it fits you. There’s no perfect way to write code, but when you get stuck, a process like this will help you get started.
(This post is based off of one I sent to my list a few months ago. To read more like it every week, join up here!)
In Using a Little Bit of Convenience to Save Your Programming Flow, I talked about how the simplest abstractions can make your codebase more fun to work with.
But what about when you have a bad abstraction?
You dig out the API reference every time to understand what that method that clearly should have been named
leaderboard is actually called. You try to refactor around it, but one part of the code hands you structured data and the other part gives you raw lines of text. And every time you touch the code, something breaks, and you blow the rest of your day trying to debug it. Because you tweaked
position, but didn’t update
score at the same time.
So how do you build good abstractions, while staying far away from the bad?
Using good metaphors to write clearer code
Good abstractions come from good metaphors.
A metaphor describes something in your system by relating it to a concept that’s already in your head. For example:
Many command lines use the metaphor of a pipe.
You put data in one end of the pipe, and it comes out the other end. Easy to imagine and easy to use.
In concurrent programming, a lock is a good metaphor.
Without knowing anything about how locks work internally, you know that when a resource is locked up, you can’t use it. You have to wait until it’s unlocked.
In Object-Oriented Programming, message passing triggers a specific mental image: An object passes another object a message. The second object can do something with the message or pass it to someone else.
You don’t need to know anything about the implementation in order to get the idea (and it makes things like the proxy pattern much easier to understand).
A good metaphor is critical. If you see an abstraction based on a good metaphor, you’ll rarely have to read the documentation or go source diving to understand the code. The better the metaphors you come up with, the easier it will be to describe your system to someone else, and the easier it’ll be to work on your system later on.
How do you come up with good metaphors?
A good metaphor is specific, self-contained, and creates a vivid mental image of the thing it describes. It doesn’t leave room for misunderstanding, and it matches the code’s behavior as closely as possible. If someone makes assumptions about the code based on their understanding of the metaphor, those assumptions should be valid.
Coming up with good metaphors is hard. You’ll often go through a few leaky metaphors before you can find the right one, and that’s OK! It will get easier with practice. But here’s a process that’s helped me:
Think about what a bit of code does, without thinking about how it does it. What image comes up in your mind? How does other code use it? If you had to describe it in a word or two, which words would you use? Write that down.
Then, think about that image. Does it fit how that code works? Are there edge cases that leak through the metaphor? Is there anything besides the metaphor a new contributor would have to understand before they could use the code? When you think about your metaphor, do you get a clear picture in your mind, or is it fuzzy?
These are indicators that your metaphor doesn’t match your code closely enough. But the closer you are to a good metaphor, the less of a leap you’ll have to make to get there.
Finally, use it in a sentence. “This code is a (something), because…” Could you explain your metaphor to someone without a technical background, and have them understand what it does? The less background someone needs to understand the metaphor, the more helpful your metaphor will be.
If you’re close, but not quite there, take a look in a thesaurus. Subtle differences between similar words can mean the difference between an OK metaphor and a perfect one.
Metaphors and OOP
Object-Oriented Programming is fundamentally based on metaphor. That’s where it gets its power! Well-designed classes don’t just hide their implementations from other classes, they hide their implementations from you.
With a great metaphor, you should be able to tell just from the class name how to use your objects. If your classes are named well, and they cause the right images to appear in your mind, you shouldn’t have to dive into their implementation to understand them.
So now, I’d like you to think about the classes in the last code you worked with. Can you think of any that have particularly clear metaphors? Which classes do you feel like you work with, rather than work against? How often do you go digging into them to figure out how to use them?
I’d love to hear what you find out. Leave a comment, and let me know.
To become a great software developer quickly, you have to consciously think about the code you’re writing and the processes you’re following. And I can help you.
Generally, global variables are bad. But sometimes, a global in the right place can make your code a lot simpler.
In Rails, you might set data once during a request, but use it in every layer of your app. Which user is making the request? What permissions do they have? Which database connection should be used?
This information should be available throughout your code, so passing it everywhere would just be a bunch of noise. But using a Ruby global variable, or setting a class variable, will cause its own problems. Multiple threads could overwrite it, and you’d end up with a huge mess, and maybe unrecoverably bad data.
You need something that’s global, but for just your request.
The plain Ruby way
Usually, you’ll see this handled with
Thread.current. It looks like this:
This is simple enough. It’s a decent solution. But it has two big drawbacks:
Someone could accidentally overwrite your data.
What if two people picked the same key? You’d stomp on each other’s data. And if you’re storing something like users, that would be seriously bad. In your own apps, this probably won’t be a problem. But if you’re writing gems, or your Rails app is big and messy, it’s something you need to think about.
It’s not well-structured.
Thread.currentis just a big bag of data. You can’t easily document it. If you want to know what you can take out of it, you have to search for what you put into it.
Of course, if you’re putting away enough global data that this is a problem, you have more to worry about than
Thread.current’s lack of structure. But it’s still a point to keep in mind.
So, is there a better solution than
Thread.current? As you might imagine, Rails itself manages a lot of request-local data. And the box of goodies that is ActiveSupport has a different pattern to follow.
The Rails way
If you dig through ActiveSupport, you might run into
ActiveSupport::PerThreadRegistry. You can probably tell from the name that this is exactly what we’re looking for.
ActiveSupport::PerThreadRegistry, the earlier example would look like this:
1 2 3 4 5 6 7 8
It’s a tiny bit more work. But with
You have a place to put documentation. Anyone looking at your class will know exactly what global data you’re expecting, and what global data they can use.
You get an API that looks good. In fact, it looks like you’re just setting class variables. That’s probably what you’d be doing if you didn’t have to worry about threads.
Any data you set on the class will be namespaced. You don’t have to worry about
PhoneNumberApiRegistry.current_user, they’re treated totally separately.
ActiveSupport::PerThreadRegistry internally, for things like ActiveRecord connection handling, storing EXPLAIN queries, and ActiveSupport::Notifications. So if you’re looking for more good examples of the power of
PerThreadRegistry, Rails itself is a great place to start.
When threads and requests don’t match
For some Rails servers, a single thread handles multiple requests. This means anything you put into a
PerThreadRegistry will stick around for the next request. This is probably not what you want.
You could do what Rails does, and clean up the things you put into the
PerThreadRegistry after you’re done with them. Depending on what you put in there, you might be doing this anyway.
This leaves the door wide open for someone to create a
PerRequestRegistry, which combines the safety of request_store with the API of
PerThreadRegistry. If someone’s done this already, I’d love to hear about it!
Try it out
Global data can be bad. Too many globals can quickly lead to unmaintainable code.
But if it makes sense to keep data in a central place for an entire request, go beyond
ActiveSupport::PerThreadRegistry or request_store a shot. If nothing else, it’ll make dealing with global data a little less risky.
each, but I have a problem with it. What happens when you have an empty collection?
If you call
.each, nothing will happen and
 will be returned. Sometimes, that’s what you want. But more often, especially when you’re building UI, you’ll want to handle empty lists in a special way. For example, I usually want to show a different message when I don’t have any data.
nil, you’re stuck writing something like this:
1 2 3 4 5 6 7
That works, but it feels awkward. I’d rather say, “Do this thing to each item in this collection, unless there’s nothing in it, in which case do this other thing entirely.” I want something like
each, with an “or else.”
Rendering Rails Collections
Inside your views, Rails can help. It’s great at rendering collections of things:
render is passed
@users, it renders
_user.html.erb once for each user inside
@users. You can skip the
As a bonus, if
@users is empty,
nil, just like we want! So you can write this, and get the same output as the original version:
It’s a lot more direct, once you understand Rails’ conventions.
What about outside of a view?
If you follow Rails,
render with a collection is a fast, powerful way to render collections of objects.
But sometimes you won’t want to deal with an extra partial to render each item. Or
render won’t support your design. Or you won’t even be inside a view.
The best solution I’ve found so far is
list.presence is the same as:
1 2 3 4 5
That is, you’ll get the list back if it has anything in it, and
nil if it’s empty.
presence, you could write:
1 2 3
This prints each name if there are users in
You don't have any users otherwise.
Still, I could do without the
presence. It feels like a hack, because it is one. If it was supported, something like this might be better:
1 2 3
or the Smalltalk-ish:
1 2 3
or even (gasp!):
1 2 3 4 5
For now, though, I usually just go with
presence or the basic
if blank?; else each pattern.
Which do you think is better? How do you handle empty lists? Have you found a better way? If so, tell me about it!
There are a lot of ways to launch a Ruby or Rails console:
bundle exec irb,
bundle console, and
rails console are some of the most common. They seem similar, but they each work a little bit differently.
If you don’t know what those differences are, you’ll have some problems. Maybe you won’t be able to get your ActiveRecord models connected to the database. Or you’ll require a file and get the wrong version. Or a library you thought would be avaliable, wasn’t.
How do you make sure you’re using the right console at the right time?
Bundler vs non-Bundler
irb is just a plain Ruby console. It doesn’t care about your
Gemfile. It doesn’t load anything but the core Ruby libraries. Anything else you want, you have to
If you install a gem using
gem install, you can require it inside
irb. If you used
bundle install, you might be able to require it, depending on where Bundler put it. (Bundler will sometimes put gems outside of Ruby’s gem path, if you run something like
bundle install --path or
bundle install --deployment).
irb ignores your
Gemfile, the versions inside your
Gemfile.lock don’t matter.
irb will load the newest version of a gem it can find:
1 2 3 4 5 6 7 8 9 10 11 12 13
This can cause really weird problems with your code, if you’re not expecting it.
irb is great if you’re messing with core Ruby files. It’s fast, and doesn’t need any setup.
But if you want to use your
Gemfile when you run a console, run
bundle exec irb instead. The
bundle exec allows
irb to load the gems that Bundler knows about, and only the gems Bundler knows about:
1 2 3 4 5
We got exactly the Rails version we were looking for.
bundler/setup vs Bundler.require
When would you run
bundle console instead of
bundle exec irb?
bundle exec irb sets things up so you can only require the gems in your
bundle console goes one step further. When you run
bundle console, you don’t even need to require the gems in your
Gemfile. They’re already required for you:
1 2 3 4 5 6 7 8 9
You could also get this behavior if you called
Bundler.require inside your
bundle exec irb console. Any gem in your
Gemfile that isn’t marked
require: false will be automatically required, and you’ll be able to use it without any extra work. When you’re working on projects with a
Gemfile, that’s incredibly convenient.
There’s still one difference to think about:
bundle console and
1 2 3 4 5 6 7 8
bundle console just requires a bunch of gems.
rails console requires those gems, but it will also load your entire Rails environment, set up autoloading, initialize your application, and give you a full Rails environment to play around in.
You can get something like the Rails console from
bundle console if you require
1 2 3 4 5 6 7
Each one, just a little more complicated
So, to recap:
irbis the basic Ruby console. It ignores your
Gemfile, and only core Ruby classes are accessible without
require-ing them. It can’t easily load gems that Bundler installs outside of RubyGems’ load path.
bundle exec irbis like
irb, if you also required
bundler/setup. You can only easily require gems that are in your
Gemfile.lock, but you can load those gems no matter where Bundler put them.
bundle consoleis like
bundle exec irb, if you also called
Bundler.require. All of the gems in your
Gemfile, except the ones marked
require: false, can be used without requiring them. It’s really convenient when you’re writing your own gems, or working on non-Rails code.
rails consoleis like running
bundle consoleinside a Rails app, if you also required
config/environment.rb. You can play with your entire Rails app, autoloads and database connections work, and everything’s hooked up the way you’d expect. If you’re working in a Rails app, this is the most helpful kind of console.
There aren’t too many differences between these consoles. And while most of those differences won’t be too big (Oh, this file isn’t required? Better require it!), others will be totally infuriating if you don’t know what’s going on. (WHY IS THIS LOADING THE WRONG VERSION OF RAKE AGAIN!?)
But if you know the idea behind each of these consoles, you’ll be able to use the right kind of console at the right time. And all the libraries you need will be close by when you need them.
Have you ever wanted to pass a method to a function that only takes a block? Or figure out which of your object’s superclasses broke the method you’re trying to call?
Those things are easy to do with the
method method. Use it well, and you can learn about your dependencies, save yourself hours of debugging, and get your code to the places it needs to be.
Methods as easy as lambdas
Lots of Ruby methods take blocks or lambdas. But you can’t directly pass a method to another method, the way you would with a lambda. You have to use
1 2 3 4
So, what’s happening here?
The first line turns the method
Math.sin into a
Just like lambdas,
Method objects respond to
Method objects respond to
to_proc (Thanks, Benoit). So they can be used in the same places you’d use a lambda:
1 2 3 4
Take a look at the first line again. This code, using a lambda, works the same way as the earlier code, using
Depending on how you’ve written your code, getting ahold of a
Method object can be a lot easier than wrapping it in a lambda. So when you need a lambda and all you have is a method, remember the
Where did that method come from?
You just called a method on your object, and it does something you didn’t expect. Maybe someone overrode it or monkey-patched it. How do you figure this out?
You could dig through the source code for a few hours. But
Method has two methods that can speed things up.
To figure out which class defined the method you’re calling, use
When you want to go a little deeper and figure out exactly where the method was defined, use
source_location returns an array. The first element is the path to the file where the method was defined, and the second element is the line number. With those, you know exactly where to look next.
Reading the source (without having to dig)
Once you can figure out where a method was defined, you might want to see how it’s defined.
Method can’t do that by itself. But if you install the
method_source gem, you can see the source code for many methods right from your console:
1 2 3 4 5 6 7
You can even see the comments:
1 2 3 4 5 6 7 8 9 10 11 12
Pretty awesome, right? Your documentation is right in the console!
Introspection is awesome
Some of my favorite Ruby classes are those that let you inspect your code from within your code. Classes like
Method. With these, you can learn a ton about your code as it runs, and modify it on the fly. Learn these classes well, study their API, and you’ll be able to do amazing things with Ruby.
When you generate a scaffold in Rails, you’ll see the usual
1 2 3 4 5 6 7
But some of your actions, like
index, don’t have them!
1 2 3 4 5
This is bad. Why? If you hit
txt isn’t supported by your app, you’ll get the wrong error:
This isn’t quite right. You should be telling the client that they’re requesting a format you don’t support, not that you can’t find the right file.
If this was a
UnknownFormat error, you could return a better response code. Instead, these errors will get mixed together with other, unrelated errors, and it’ll be really hard to handle them.
You could add a
respond_to block to your
1 2 3 4 5 6 7 8 9
Then, you’d get the exception and error code you’d expect:
1 2 3 4 5 6
Much better. But littering all your controllers with
respond_to is crazy. It feels un-Rails-ish. It violates DRY. And it distracts you from the work your controller is actually doing.
You still want to handle bad formats correctly. So what do you do?
If you’re not doing anything special to render your objects, you can take a shortcut. If you write:
1 2 3 4
it works the same way as writing the full
respond_to block in
index. It’s a short way to tell Rails about all the formats your action knows about. And if different actions support different formats, this is a good way to handle those differences without much code.
Handle formats at the controller level
Usually, though, each action in your controller will work with the same formats. If
index responds to
json, so will
create, and everything else. So it’d be nice if you could have a
respond_to that would affect the entire controller:
1 2 3 4 5 6 7 8 9
And this actually works:
1 2 3 4 5 6
Exactly the kind of error we were hoping to get! And you didn’t have to mess with each action to do it.
Sometimes you’ll want to do different things depending on the state of a model. For instance, for
create, you’d either redirect or re-render the form, depending on whether or not the model is valid.
Rails can handle this. But you still have to tell it which object you want it to check, with
respond_with. So instead of:
1 2 3 4 5 6 7 8 9 10 11 12 13
you can write:
1 2 3 4 5
This way, you separate your code from the formats you respond to. You can tell Rails once which formats you want to handle. You don’t have to repeat them in every action.
The responders gem
In Rails 4.2, there’s a catch:
respond_with is no longer included. But you can get it back if you install the
responders gem. And the
responders gem brings some other nice features with it.
You can set flash messages in
respond_with by including
responders :flash at the top of your controller:
Conveniently, you can set defaults for these flash messages in your locale files.
Also, if you have the
responders gem in your
Gemfile and you generate a Rails scaffold, the generator will create controllers using
respond_with instead of
1 2 3 4 5 6 7 8 9 10 11 12 13 14
This is a lot cleaner than the scaffolds Rails comes with.
If you want to return different information for different formats, you have a few options. The controller-level
respond_to combined with
respond_with is a great way to get short controllers. But it tends to help the most when all of your controller actions respond to the same format, and act in the way Rails expects them to.
Sometimes, though, you want to be able to have a few actions that act differently. The one-liner
respond_to is great for handling that situation.
If you need more control, use the full
respond_to with a block, and you can handle each format however you want.
With any of these, requests for formats you don’t support will get the right error. And both your app and its clients will be a lot less confused.
You’re working with a brand new library or feature, and you just can’t understand the documentation. Maybe it’s hard to get set up, so you have to bounce between RDocs until you learn how everything fits together. Maybe the documentation doesn’t even exist. Or maybe you just learn best by seeing lots of examples.
You need some help. But where do you learn how to use a feature, if not its documentation?
One of my favorite GitHub features
When you want to go beyond the documentation and see real-world uses of a library or feature, use GitHub’s code search.
For example, say I just read the Rails guide on Conditional GET Support. I feel like I understand it, but I want to see some more examples before I use it for real.
I could hop onto GitHub search, and look for
"if stale?". Then, in the sidebar, pick
And now I have tons of great examples of how
stale? could be used.
Not all of the examples are interesting. But in one listing, I notice the author using
1 2 3 4 5 6 7
That seems like a great way to use
stale?. And if I hadn’t seen this example, I might not have thought to try it.
How should you use what you find?
When you search open-source code, you get hundreds of examples for nearly any feature. You’ll notice where the documentation makes sense, and where it’s over-simplified. And you’ll see objects working together in real code. That will teach you more about how classes are connected than any RDoc can.
Still, use the examples to learn new things, not to cargo cult into your own project. Using code without understanding it is the fastest way to a poorly designed system.
In my example,
maximum might not be the right thing to do in my project. I shouldn’t copy the line without learning more about it. Without understanding when it makes sense to use. But examples can teach you things you didn’t know before. And they’re a great way to decide what to learn about next.
Like any search, it’ll take some practice to get good results. But I’ve learned a lot from the code I’ve found, and I know you will too.
When I was first learning the board game Go, I bought and read a few introductory books. They taught me the rules, and some basic strategy. I knew that there was a fun game there. But I just wasn’t seeing it.
When it came time to play my own games, I was confused and stressed out. There were 361 possible moves I could make, but which of those was the best one? Not even that, which of those were even acceptable? Using the information I got from the first books, I could maybe narrow decent moves down to about a hundred, but I still had trouble knowing where to start, and what to do. And it just wasn’t fun to me.
I felt like I couldn’t even play the game unless I studied everything first.
So, on a friend’s recommendation, I picked up another book. The book was called “The Second Book of Go.” It had short chapters, each on a specific topic that beginners tend to struggle with. And it gave me some processes I could follow, so I could know that I wasn’t making insane moves.
It taught me the reason behind crazy-looking strategies, and it taught me tricks to make better decisions and evaluate my own moves. And it showed me that even wrong moves had value.
When I started playing again, I began to have fun.
As long as I’ve written here, I’ve heard from people learning Rails that were in the same boat as I was learning Go. They heard it was supposed to be fun, but it was actually just frustrating. They didn’t know where to start. The things they learned didn’t stick with them. They couldn’t spend time learning consistently enough to grow as a Rails developer. Most of all, they were overwhelmed, because the space of possibilities in front of them seemed too big.
If this sounds familiar, I wrote a book for you. It’s meant to bring the joy of Ruby and Rails to those who were about to give up. To show that with a little structure, a little practice, and a little curiosity, you can write your own Rails applications, and have fun doing it.
It’s available for pre-sale, starting today. It’s 25% off until it’s finished. And when you buy it, you’ll get today’s draft immediately, and updates to it from here on out.
I hope you enjoy it.
So, you’re working on a new app, and Rails just generated a test for you:
1 2 3 4 5 6 7
You uncomment it, come up with a name, and you’re ready to write your test, right? But then what? What do you write first? What should your test code look like?
If you follow a simple pattern, you’ll turn those stubbed out lines of code into clear, well-structured test cases.
The three-phase test pattern
Your test cases should work in three phases:
- First, you set some stuff up (“Arrange”)
- Then, you do something (“Act”)
- Then, you make sure that what you expected to happen, actually happened. (“Assert”)
For instance, imagine you were testing a method on an array in Ruby. Following this pattern, your test could look like:
1 2 3 4 5 6 7 8 9 10
Simple enough. But every part of the test has a place to go, and each stage of the test almost tells you how to write it.
Sometimes, you won’t need an Arrange phase, or the Act and Assert phases will be combined. But it still helps to think about all three phases as you write your tests.
The Assert phase gotcha
There’s a trick to the Assert phase: you shouldn’t use the same logic that the Act phase used in the Assert phase. You should always take two paths to the same answer. Otherwise, you won’t notice bugs in the code you’re calling, because it’s just getting called again in the Assert phase.
For example, if you’re doing some math:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1, 2, 3, 4].average again in the Assert phase is bad, because
average could return almost anything and that assertion would still pass.
Here, that’s pretty clear. But even when things get more complicated, make sure you’re not just running the same code twice. Otherwise you’re only verifying that your method was called, not that it worked the way you expect it to.
Usually, the easiest way to take a second path to the answer is to find the answer by hand and hardcode it. It can be brittle, but it’s better than your tests breaking without you realizing it.
Why three phases?
If you split your tests into those three phases, you have simpler questions to answer. Instead of “How should I write this test?”, you can focus on each phase: “How should I set this test up?”, “What am I testing?”, “What should the answer look like?”
These questions still might not have easy answers, but the answers will be a lot easier than thinking about the entire test at once. And if you’re lucky, you can even share phases between related tests, making your next test much less painful to write.