Building up tests via context and let in Ruby
One of my favorite parts about Ruby is writing tests. Here's a pattern I use frequently, which allows you to use context and let to build up state as you progress deeper into the context tree. This is best described with an example:
What's going on here?
First of all, I like to always add a describe method block for each public method of the class, and then inside it, declare a subject variable which calls that method. Then I add a let(:...) for each of the arguments to the method. This allows me to setup some sensible defaults for each argument, and only modify the arguments as needed in each context. First, I test the default arguments, like in the context "with defaults". Then I proceed to test modifications to each argument. For example, the context "with nil player" expects the described method to return nil when a nil player is passed in.
Where things get more interesting is the context "with other existing character". Here, we have a before statement which gets triggered before all of the tests in that context. It sets up state such that there's already another character generated. It expects each sub-context to declare a new variable, existing_name, and generates an object with that name. Then we have two contexts where existing_name either matches or does not match the name being generated by the function call. Under the name matches context, we have another sub-context for when that existing character is under a different player, which is treated differently. You can easily nest many sub-contexts, each with a useful description of the state that they are in. As a result, we can test all the different states of our class or database with fine granularity. That's where I find the sort of pattern most handy: checking for all the different states that are possible by changing each state option one at a time.
Finally, we can easily test each of our validations this way. The simplest example was the nil player near the beginning, which yields a nil return. Another example is the name containing invalid characters context. Here we have a very simple context which defines the expectation that the class will raise an exception when an invalid name is passed in.
Comments
Post a Comment