I just hit a real mean snag today trying to put an expectation on a model and it didn’t get caught like it should. This is something I do all the time so I knew I wasn’t just making a basic typo. Here’s what my setup looked like:
The model
def purchase send_shipping_notice end def send_shipping_notice puts "hi" end
In my spec/test
def given_bought_on(date) wp = @order.product puts wp.inspect wp.should_receive(:send_shipping_notice) wp.purchase end
The output
#<Product id: 270, owner_id: 270, name: 'thing'> hi 1) Spec::Mocks::MockExpectationError in 'Product should purchase' Mock 'Product' expected :send_shipping_notice with (any args) once, but received it 0 times ./spec/models/product_spec.rb:21:in `given_bought_on' script/spec:4:
So as you can see, I put an expectation on the wp
variable, and verify it gets called by seeing ‘Hi’ get printed. However the expectation totally gets overlooked and my test framework complains that it never happened.
The catch is that the wp
in my test and the web_page self
inside the model are two different models. So the expectation is on a different model. Calling .object_id
or .inspect
on either one will affirm (or rather lie) to you that they’re the same, but they are not.
This is (as I’m told) well documented in ActiveRecord and is intentional by design. The wp
variable is in fact an Association Proxy, not the WebPage I think it is. It just lies to you outright so you think it’s a WebPage.
My solution, while rather a hack, is the only thing I know to get my spec to pass:
def given_bought_on(date) wp = Product.find(@order.product_id) puts wp.inspect wp.should_receive(:send_shipping_notice) wp.purchase @order.reload end
Now it grabs the model directly and there’s no confusion.