Thursday, May 14, 2009

Adjusting My Expectations

‹prev | My Chain | next›

Picking up where I left off last night I need to build up the meal listing for a given year. I plan to ask a CouchDB view for this list. The example describing this is:
      it "should ask CouchDB for meal from year YYYY" do
RestClient.
should_receive(:get).
with(/year=2009/).
and_return("{ }")

get "/meals/2009"
end
That's pretty straight forward—a request of the Sinatra "/meals/2009" action should, in turn, fire off a request to the (as yet unwritten) CouchDB view that will accept a parameter of year=2009. Before implementing this example, I run the spec to determine if I need to change the message or make it pass:
cstrom@jaynestown:~/repos/eee-code$ spec ./spec/eee_spec.rb
.F...............

1)
Spec::Mocks::MockExpectationError in 'eee a CouchDB meal GET /meals/YYYY should ask CouchDB for meal from year YYYY'
RestClient expected :get with (/year=2009/) but received it with ("http://localhost:5984/eee-test/id-2009-05-13")
./spec/eee_spec.rb:36:

Finished in 0.207576 seconds

17 examples, 1 failure
Unfortunately, I need to change an entirely unexpected message here. I have yet to add any code to the Sinatra action, so where on earth is that RestClient#get originating?

The answer is the after(:each) block that is responsible for tearing down the meal(s) created for the examples:
    after(:each) do
data = RestClient.get "#{@@db}/#{@permalink}"
meal = JSON.parse(data)

RestClient.delete "#{@@db}/#{@permalink}?rev=#{meal['_rev']}"
end
Oh man, I need to get twice—once in the example and once in the after(:each) tear down—but I want to stub one call and let the other pass through to the DB. That does not sound easy.

The best that I can come up with is to un-stub the second call to RestClient#get by invoking the alias created by RSpec. When RSpec assigns a stub or expectation, it performs an alias_method_chain-like renaming of the original method by prefixing it with proxied_by_rspec__. Thus, the original, non-stubbed version of RestClient#get is available as RestClient#proxied_by_rspec__get.

Putting this knowledge to use, I can leave the expectation in the example, but modify the after(:each) example code block to make a real RestClient#get request:
    after(:each) do
data = RestClient.proxied_by_rspec__get "#{@@db}/#{@permalink}"
meal = JSON.parse(data)

RestClient.delete "#{@@db}/#{@permalink}?rev=#{meal['_rev']}"
end
Sadly, with that in place, I am in even worse shape than before:
cstrom@jaynestown:~/repos/eee-code$ spec ./spec/eee_spec.rb
FF...............

1)
NoMethodError in 'eee a CouchDB meal GET /meals/YYYY should respond OK'
undefined method `proxied_by_rspec__get' for RestClient:Module
./spec/eee_spec.rb:35:

2)
RestClient::RequestFailed in 'eee a CouchDB meal GET /meals/YYYY should ask CouchDB for meal from year YYYY'
HTTP status code 409
/home/cstrom/.gem/ruby/1.8/gems/rest-client-0.9.2/lib/restclient/request.rb:144:in `process_result'
/home/cstrom/.gem/ruby/1.8/gems/rest-client-0.9.2/lib/restclient/request.rb:106:in `transmit'
/home/cstrom/.gem/ruby/1.8/gems/rest-client-0.9.2/lib/restclient/request.rb:103:in `transmit'
/home/cstrom/.gem/ruby/1.8/gems/rest-client-0.9.2/lib/restclient/request.rb:36:in `execute_inner'
/home/cstrom/.gem/ruby/1.8/gems/rest-client-0.9.2/lib/restclient/request.rb:28:in `execute'
/home/cstrom/.gem/ruby/1.8/gems/rest-client-0.9.2/lib/restclient/request.rb:12:in `execute'
/home/cstrom/.gem/ruby/1.8/gems/rest-client-0.9.2/lib/restclient.rb:65:in `put'
./spec/eee_spec.rb:29:

Finished in 0.230206 seconds

17 examples, 2 failures
Gak! I do not have a stub in my first example, so there is no RestClient#proxied_by_rspec__get method available in the after(:each) block. Since the meal is not deleted after the first example, it is still in place when the second example tries to create it, which is the reason for the second failure.

To change this message I need to add a stub in the before(:each) block to pair with the un-stub in the after(:each) block:
    before(:each) do
@date = Date.new(2009, 5, 13)
@title = "Meal Title"
@permalink = "id-#{@date.to_s}"

meal = {
:title => @title,
:date => @date,
:serves => 4,
:summary => "meal summary",
:description => "meal description"
}

RestClient.put "#{@@db}/#{@permalink}",
meal.to_json,
:content_type => 'application/json'

RestClient.stub!(:get).and_return("{ }")
end
Finally, I get the failure that I was expecting from the outset—the RestClient call to CouchDB has not yet been implemented:
cstrom@jaynestown:~/repos/eee-code$ spec ./spec/eee_spec.rb
.F...............

1)
Spec::Mocks::MockExpectationError in 'eee a CouchDB meal GET /meals/YYYY should ask CouchDB for meal from year YYYY'
RestClient expected :get with (/year=2009/) once, but received it 0 times
./spec/eee_spec.rb:51:

Finished in 0.167963 seconds

17 examples, 1 failure
Now, instead of changing the message, I can make it pass by adding the RestClient call:
get %r{/meals/(\d+)} do |year|
url = "#{@@db}/_design/meals/_view/by_year?group=true&year=#{year}}"
data = RestClient.get url
@meals = JSON.parse(data)
end
And, indeed, it is now passing:
cstrom@jaynestown:~/repos/eee-code$ spec ./spec/eee_spec.rb
.................

Finished in 1.838225 seconds

17 examples, 0 failures
That is a good stopping point for tonight. I would have preferred to have made some progress on the views (Haml and CouchDB), but I know where to pick up tomorrow.

(commit)

No comments:

Post a Comment