Rails and RSpec: Testing controllers with the same

2019-07-11 19:09发布

问题:

I have rails 4.1.16 API application that is tested using RSpec 3.4.0, and I experience problems with testing classes called the same name in a different module.

The structure is:

app/controllers/bar/notifications_controller.rb

class Bar::NotificationsController < ApiController
  ...
end

and controller with the same name in a different module:

app/controllers/foo/bar/notifications_controller.rb

module Foo
  class Bar::NotificationsController < ApiController
    ...
  end
end

The Foo is a new module and does not have tests yet. After adding it, all the corresponding controller tests for the old Bar::NotificationsController started to fail.

The spec file:

spec/controllers/bar/notifications_controller_spec.rb

require 'spec_helper'

describe Bar::NotificationsController, type: :controller do
  ...
end

All the tests in that spec file fail with the same error:

RuntimeError:
   @controller is nil: make sure you set it in your test's setup method.

The problem does not exist when I change the controller name in the Foo module:

app/controllers/foo/bar/foo_notifications_controller.rb

module Foo
  class Bar::FooNotificationsController < ApiController
    ...
  end
end

I already tried adding on top of the spec file require 'bar/notifications_controller' and using the class name as a string describe "Bar::NotificationsController, type: :controller but it did not solve the issue (the same error).

Why is this happening? What is the solution?

I want to believe there is a tiny thing I did not try yet and I don't have to pollute my code and the structure with nonsense names just to make the specs pass.

Many thanks in advance for your help!

回答1:

In general, I've take to including all namespacing in the class definition. Something like:

app/controllers/foo/bar/notifications_controller.rb

class Foo::Bar::NotificationsController < ApiController
  ...
end

While, at first glance, this might look the same as:

app/controllers/foo/bar/notifications_controller.rb

module Foo
  class Bar::NotificationsController < ApiController
    ...
  end
end

These are, in fact, different. The difference is in how Rails handles autoloading of constants. I won't go into the details here because it's a longer topic and there are good articles/posts out in the web-o-sphere.

You can find good articles on how Rails handles autoloading like this one (or try Googling rails constant loading)

Also, as the article notes, Ruby constant loading operates differently than Rails loading. Good information on Ruby constant loading can be found here (or try Googling ruby constant loading).