Question

I want to test an inclusion of a module into a class. I am trying define a new class in RSpec:

describe Statusable do
  let(:test_class) do
    class ModelIncludingStatusable < ActiveRecord::Base
      include Statusable
      statuses published: "опубликовано", draft: "черновик"
    end
  end

  describe '#statuses' do
    it 'sets STATUSES for a model' do
      test_class::STATUSES.should == ["опубликовано", "черновик"]
    end
  end
end

And I get an error:

TypeError:
       [ActiveModel::Validations::InclusionValidator] is not a class/module

This is probably because in Statusable I have:

validates_inclusion_of  :status, :in => statuses,
            :message => "{{value}} должен быть одним из: #{statuses.join ','}"

But if I comment it out, I get:

TypeError:
       ["опубликовано", "черновик"] is not a class/module

Maybe new class definition isn't the best option, what do I do then? And even if it's not, how can I define a class in RSpec? And how do I fix this error?

Was it helpful?

Solution

Do not define new constant in tests otherwise it will pollute other tests. Instead, use stub_const.

Also, for this is an unit test of Statusable module. If ActiveRecord model is not a necessity, better not to use it.

You can also use class_eval to avoid not opening this class(no matter fake or not) actually

describe Statusable do
  before do
    stub_const 'Foo', Class.new
    Foo.class_eval{ include Statusable }
    Foo.class_eval{ statuses published: "foo", draft: "bar"}
  end

  context '#statuses' do
    it 'sets STATUSES for a model' do
      FOO::STATUSES.should == ["foo", "bar"]
    end
  end
end

Though I copied your assertion, I would suggest not to insert a constant say STATUS into the class/module(Foo) who includes this module. Instead, a class method would be better

expect(Foo.status).to eq(["foo", "bar"])

OTHER TIPS

It fails because class definition does not return itself.

$ irb
> class Foo; 1 end
 => 1

you need to do like this:

  let(:test_class) do
    class ModelIncludingStatusable < ActiveRecord::Base
      include Statusable
      statuses published: "опубликовано", draft: "черновик"
    end
    ModelIncludingStatusable # return the class
  end

It works but unfortunately, ModelIncludingStatusable will be defined on top-level because of ruby rule.

To capsulize your class, you should do like this:

  class self::ModelIncludingStatusable < ActiveRecord::Base
    include Statusable
    statuses published: "опубликовано", draft: "черновик"
  end
  let(:test_class) do
    self.class::ModelIncludingStatusable # return the class
  end

It works perfectly :)

When you call let this define a memoized helper method. You can't class definition in method body.

Another option which I frequently use is to put the entire test in it's own module, e.g.

module Mapping::ModelSpec
  module Human
    Person = Struct.new(:name, :age, :posessions)
    Possession = Struct.new(:name, :value)
  end

  RSpec.describe Mapping::Model do
    it 'can map with base class' do
      person = Human::Person.new('Bob Jones', 200, [])

      ...
    end
  end
end

While this is a bit cumbersome, it avoids polluting the global namespace, is only slightly more syntax, and is generally easy to understand. Personally, I'd like a better option.. but I'm not sure what that would be.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top