I’m a huge proponent of test driven development and have been feeling sort of guilty about a gem that I wrote which does not encourage testing whatsoever. The gem, ClassyEnum, provides class-based enumerator functionality on top of Active Record. This blog post is not so much about the gem itself, so If you’re interested in reading more about it, the README has some good examples.
Historically, ClassyEnum has had a built-in generator to quickly create classes that represent enum members. While this has served me well, it has never generated any spec files along with these classes. I always end up either creating them manually, or just forgoing tests altogether. Neither option was great, so I wanted to see what it would take to create spec files automatically, similar to how Rails can generate model specs when using the model or scaffold generators.
When ClassyEnum is installed in your Rails project, you can run the generator like so:
1 2 3
Which produces the following file:
1 2 3 4 5 6 7 8 9 10 11
Each Priority subclass represents an enum member, and behaves like a true Ruby class. This boilerplate code starts out innocent enough, as most code does, but over time, I find myself adding logic and properties to these classes. Depending on how lazy I am, sometimes I test it, sometimes I don’t.
Exploring the Unspectacular Generator
The code for the “main” generator in ClassyEnum is fairly straightforward. It has a description, takes a few arguments, and copies a dynamically generated file into app/enums, creating the directory if it does not exist.
1 2 3 4 5 6 7 8 9 10 11 12 13
Making the Generator Spectacular
I had four main requirements for my generator’s new behavior:
- It must automatically create specs in spec/enums when the generator is run.
- The specs it creates must work out of the box (even if they are pending).
- It cannot break existing behavior.
- It must be future proof by not relying on or hacking internal Rails or Rspec code
After digging around on Stack Overflow and reading the Rails Guide,
I discovered that Rails exposes a
method. When passed the
the main generator can automatically figure out which test framework your application is
using, and based on naming conventions, which spec generator to load.
All I had to do was add the hook to my existing generator, and create
some support files to go along with it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
More information about this hook can be found in the “Customizing Your Workflow” section of the Rails Guide.
After adding just the hook, I knew it wasn’t going to work, but because I like taking baby steps when coding, I rebuilt and installed the gem, updated my project’s ClassyEnum dependency, and ran the generator anyway:
1 2 3 4
This error is just saying that the test_framework generator could not be found, which was expected because I had not created it yet. Since I don’t have any tests for the generator itself, I used this message as a failing test, and my passing test would be when the enum spec was generated.
According to the Rails Guide, the
hook_for method will search in a
few places for the generator, looking for one of a few different
class names. By default it looks for a class named after the generator that
invoked the hook, namespaced with the test framework’s class name and “Generators”.
In my case this would be
I could have alternatively used the
:as => option to specify
a different class name, but I wanted to use the default.
I needed the behavior of the enum spec generator to basically mimic that of my enum class generator, the only difference being the spec location and which template was used. My final Rspec generator class is shown here:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
And the spec template file:
1 2 3 4 5 6
After adding these two files to ClassyEnum, I reinstalled and reran the generator in my project and everything worked!
Here is my “passing” test:
1 2 3 4 5 6
Which generates the following spec file:
1 2 3 4 5 6 7 8 9 10 11 12 13
Since I am using the hook_for method, it includes some other behavior,
such as supporting the
--skip-test-framework flag out of the box. This
would allow someone to generate the enum classes without any specs, but
I don’t recommend doing that. :)
I’m happy with ClassyEnum generator once again. It creates spec files by default which makes me feel like I’m able to practice what I preach. I was able to achieve all four of my goals without any crazy hacks or making any sacrifices. I was also able to easily add support for TestUnit by adding an additional generator and template file.