An Object Quacks Like a Duck

I’ve been toying around with the idea of spec’ing mixins: that a class includes a module. Suppose the following class:

1
2
3
4
5
6
7
8
9
10
11
12
class FooList
include Enumerable
attr_accessor :some_array
def initialize(opts)
@some_array = opts[:the_array] || []
end
def each
@some_array.each { |item| yield item }
end
end

We can test the behavior of the each method using RSpec, but we can also make sure that FooList actually acts like an Enumerable. Here’s a quick RSpec Matcher just for that (require it in your spec_helper.rb)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Spec::Matchers.define :quack_like do |mod|
match do |instance|
mod.instance_methods.inject(true) { |accum, method| accum && instance.respond_to?(method) }
end
failure_message_for_should do |instance|
"expected the class #{instance.class.name} to include the module #{mod}"
end
failure_message_for_should_not do |instance|
"expected the class #{instance.class.name} not to include the module #{mod}"
end
description do
"expected the class to behave like a module by responding to all of its instance methods"
end
end

This allows us to spec some quacking:

1
2
3
4
5
6
7
8
9
describe FooList do
def foo_list
@foo_list ||= FooList.new
end
it "quacks like an Enumerable" do
foo_list.should quack_like Enumerable
end
end

I am still experimenting with this. In a way it is not really testing behavior, but it’s not really testing the implementation either. In other words, if every method in Enumerable is implemented in FooList and we remove the include Enumerable line, the spec still passes.

I’ve discussed this over IRC with some other smart folks, but I want more input . Do you think this is appropriate? Useless?