3
votes

For default Rails, you can add so called ActiveModel::Lint::Tests to see if your models adhere to parts of the ActiveModel.

I would like to call these, or an equivalent thereof in my Rspec tests.

  • I don't want to test the exact behaviour: the lint tests only spec that a certain interface exists. I want to only spec that the interface exists.
  • I rather not test too close implementation. Merely testing that a module is included is not really the same as testing an interface exists.

Is there a trick to run and include the Rails core ActiveModel::Lint::Tests in rspec examples? Are there alternatives, specially built for Rspec that run such lint-tests?

Some background:

I am building models that don't inherit from Activerecord::Base, but instead act as factories or state-machines. In Rails also known as Service-Objects. These should feel like ActiveModels for the user. Things like

@services << CompositeService.new(name: 'foo', children: [{ name: 'bar' }])
render @services

should be possible. Such a CompositeService should then, have activemodel naming, partial paths, caching-ids and so on.

1

1 Answers

2
votes

I am writing a gem that provides ActiveModel-compliant models and using RSpec 3.x.

To get the lint tests, I added activemodel as a development dependency. That also brings in ActiveSupport and with it Minitest. I figured I could use something like this gist as I have in the past, but ran into an issue trying to get the tests to use Minitest's assert.

I ended up throwing this together (credit to the gist's author) and putting it into spec/support:

require 'active_model/lint'
require 'minitest'

RSpec.shared_examples_for ActiveModel do
  include ActiveModel::Lint::Tests
  include Minitest::Assertions

  alias_method :model, :subject

  attr_accessor :assertions

  before(:each) { self.assertions = 0 }

  ActiveModel::Lint::Tests.public_instance_methods.map(&:to_s).grep(/^test/).each do |m|
    it(m.sub 'test_', 'responds to ') { send m }
  end
end

This seems to be working so far, I get error messages like this:

Failure/Error: it(m.sub 'test_', 'responds to ') { send m }
Minitest::Assertion:
 The model should respond to to_key

It's not ideal, but it's fine with me for now. The lint tests are so simple that it would be pretty trivial to implement a shared example from scratch (see the source), but this way I can just rely on the gem to keep the tests up-to-date.