Saturday, July 29, 2017

How To Test-Drive Rails Generators?

Rails generators are a form of meta-programming in Ruby. They're basically programs that write programs. To be more specific, in my open-source project Ultra Light Wizard (also a pattern presented at RailsConf 2014), the Rails generator ultra_light_wizard:scaffold generates models, controllers, views, routes, helpers and a migration automatically when the user wants to build a wizard.

How does one test-drive such a Rails generator though?

I started by writing a standard RSpec test and then quickly realized that if I were to test-drive what each file generated contains, I'd be opening a can of worms and getting lost in so much unimportant detail.

Then, I had an idea. Why not create a full-fledged Rails app as a fixture and place it under the "spec/fixtures" directory?

Next, how about I configure the app exactly as needed to run the generate command from a test, but then during the test, I leave it untouched and copy it on every test-run to avoid messing up the reference version?

Well, to explain, you end up with an RSpec test that looks like this:

Now, comes the tricky part. How do you test by invoking the generator on the project copy?

Well, I took the easy way out and simply dropped down to the shell from Ruby as follows:

In other words, by invoking "rake" from the Rails project copy, I started another RSpec test suite run from within my main test suite. Totally meta, eh!?

Now, here comes the meat of the work, in the form of feature specs written for the Rails project copy. To explain, the tests shown below are written from the user's point of view for the effect of running the generator in a real Rails app (the top part consists of helpers [e.g. fill_in_project_fields] and the bottom part has the test cases as "scenarios" [e.g. scenario 'can start wizard']):

And with that... problem solved. Here is an example test run:

Notice how the inside RSpec test-run (from the reference Rails project copy) reported 5 examples, and then the outer RSpec test-run reported 1 example (the one that spawned the rest).

Pros to this approach are success in covering end-result of generation, and decoupling tests from detailed work of generator.

Cons are obviously increased complexity, albeit that is balanced with decreased complexity of writing the inner integration specs and freedom of implementation.

How have you handled Rails generator testing in the past? Care to discuss pros and cons?

No comments: