What would you need to do to Rails to make spec files live alongside the code they describe?

Viewed 120

For years I've followed the rails convention of putting spec files in a spec folder and entirely separate from the code they describe:

- app
  - controllers
    - application_controller.rb
  - models
    - user.rb
- spec
  - controllers
    - application_controller_spec.rb
  - models
    - user_spec.rb

However there's a lot of unnecessary dislocation and confusion that can come with that approach and I really like Angular's approach of keeping components, their specs and code side by side. Having worked on large rails projects, the dislocation between spec and code can become incredibly confusing and disorientating.

What changes would be needed to make this happen?

I'd like to experiment doing the same thing in rails and moving the specs to sit next to the files they describe so that the directory structure is more similar to this:

- app
  - controllers
    - application_controller.rb
    - application_controller_spec.rb
  - models
    - user.rb
    - user_spec.rb

What things will I need to change to make this work?

A couple of things that spring to mind are:

  • making sure Zeitwerk doesn't load spec files into memory on app load
  • adjusting guard so that it watches the correct directories

What other things would I need though and is there anything in particular I should watch out for?

3 Answers

It may be possible, but probably not practical.

First off, assuming you're using rspec, you would have to configure/override the path where your test suite expects to find your spec files, which is probably not all that difficult to do, but if you're using any extensions to rspec, (or any other test suite), you would have to make adjustments to any references to spec paths in order to make your specs run correctly, which could add to future maintenance efforts.

You may also lose the ability to use generators, unless you patch the classes that create spec files when you run a generator, since they will, by default, put spec files in spec/models, spec/controllers, etc.

Overriding these kinds of things is possible, but will require maintenance, and would probably be considered 'not the Rails way' by most Rails developers. Rails is an opinionated framework, which is to say that any developers working on your app would almost certainly expect to see spec files in the spec directory by default, so you might get pushback from your future team members.

There may be other issues, but those are just the first things that come to mind.

Why use autoload? Since you're going so far away from the framework's convention, you could simply require files manually, and configure rspec to run only on _spec.rb files. rspec ./app and you're good to go.

-

A note about architecture:

Bob Martin points out in his book "Clean Architecture" that tests can be considered the outermost layer of your system. They're there to guarantee it works properly, but absolutely NOTHING depends on them. Changing the structure of your whole project, tied to one web-framework, to tie it to the convention of another web-framework, doesn't appeal too much to me.

Here's a quite old talk from him for the rails community, touching topics such as this: https://www.youtube.com/watch?v=WpkDN78P884

https://github.com/RailsEventStore/cqrs-es-sample-with-res

This sample application from the RailsEventStore organization provides an example of how to go about it, though it uses mini-test instead of rspec, in theory it should work for rspec.

It provides an example of using /ordering at the top level to group logic. Specific for this, it uses /ordering/test/ to store the tests. It includes a test helper which basically just loads the stuff you could include in the regular test helper. So for rspec I imagine it would be something like:

# rails_root/something/rspec/spec_helper.rb
require_relative '../../rspec/rspec_helper' # Load the standard rspec helper

This allows you to continue to use the standard /rspec pattern while also being able to have high level modules (ie: bounded contexts) include their own specs.

Though really, I think that is probably a bad practice as far as how you had it. It would likely make more sense to do something like below, though even that isn't really ideal as you add duplicate spec_helpers and there really isn't a practical benefit to grouping them that way other than it being slightly faster to find specs in some cases.

- app
  - controllers
    - application_controller.rb
    - specs
      - application_controller_spec.rb
      - spec_helper.rb
  - models
    - user.rb
    - specs
      - user_spec.rb
      - spec_helper.rb
    

You would run the model specs via rspec app/models/specs.

Related