Skip to content


RSpec matcher for Active Record associations

Lazy testing

<p>You can test your associations lazily.  Lazy isn&#8217;t always a bad thing, and for some situations,

it works perfectly.

1
2
3
4
5
6

# has_many side
foo.should respond_to(:bars)

# belongs_to side
bar.should respond_to(:foo)
<p>This does work.. kinda.  But it doesn&#8217;t feel right because we aren&#8217;t actually asserting

that bars and foo are associations.

<h4> Custom matcher</h4>


<p>While researching links for this post, I found some interesting code at the

spicycode blog. It appears we have the same idea with completely different implementations. I’m going to show my partial solution here, and you can venture yonder if you want to see it from another point of view.

1
2
3
4
5
6

# Simply put, I wanted to do the following:
foo.should have_many(Bar)

# and I wanted to do this too:
foo.should have_many(Bar).as(:quirks)
<p>So, I shelled out some code..</p>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# matchers are plain old ruby classes
class BeAHasMany

  # this is what the matcher is called on.
  # In this case: 
  #   foo.should have_many(:bars)
  # foo would be passed to the +initialize+
  def initialize(expected)
    @expected = expected
  end

  # the work horse of the class.
  # 
  # peforms the actual matching using the option +as+ method if applicable.
  def matches?(actual)    
    @actual = actual
    association_name = @as.nil? ? @expected.to_s.tableize : @as
    
    reflection = actual.reflect_on_association(association_name.to_sym)
    if reflection.macro == :has_many
      reflection.klass == @expected
    else
      false
    end
  end
  
  # allows you to change the name of the assocation
  # example:
  #  foo.should have_many(Bar).as(:quirks)
  def as(a)
    @as = a
    self
  end

  # error message for should
  def failure_message
    "expected #{@actual.inspect} to have_many #{@expected.inspect}, but it didn't"
  end

  # error message for should_not
  def negative_failure_message
    "expected #{@actual.inspect} not to have_many #{@expected.inspect}, but it did"
  end
end

# This method is the one you use with should/should_not
def have_many(expected)
  BeAHasMany.new(expected)
end

Posted in Uncategorized.