3

I'd like to override the get and post methods in RSpec.

I want to do this in order to deal with subdomains in my tests. As far as I can tell, the only way to deal with subdomains is to alter the @request object before each call. I could do this before each and every test but that's going to lead to some really messy code.

In an effort to keep things DRY I've tried using a config.before(:each) method in spec_helper.rb however this doesn't seem to be run in the same scope as the test and doesn't have access to @request.

My next bsest approach is therefore to overrride get and post which are in the correct scope.

def get *args
  @request.host = @required_domain if @required_domain
  super *args
end

I can include this code in the top of each spec file but I'd rather set it universally. If I set it in spec_helper.rb though it does not get called.

Where can I set this to override the default get method?

Peter Nixey
  • 14,568
  • 11
  • 71
  • 118

3 Answers3

4

however this doesn't seem to be run in the same scope as the test.

That's not quite right - it's run in the same scope, but before @request is configured, so it has no effect.

Try this:

module RequestExtensions
  def get(*)
    @request.host = @required_domain if @required_domain
    super
  end
end

RSpec.configure do |c|
  c.include RequestExtensions, :type => :controller
end

HTH, David

David Chelimsky
  • 8,572
  • 1
  • 35
  • 30
  • That worked great thank you! I've edited your answer slightly just to include the call to `super` in the `get` method which I presume is still required? – Peter Nixey Dec 09 '11 at 17:17
3

I just ran into an issue which routed me to this question. The accepted solution guided me to the more effective implementation as of rack-test 0.6.3.

I manually created ./spec/helpers/rspec_http_request_override_helper.rb

module RspecHttpRequestsOverrideHelper

  def get(uri, params = {}, env = {}, &block)
    super(uri, params, set_json_headers(env), &block)
  end

  def post(uri, params = {}, env = {}, &block)
    super(uri, convert_to_json(params), set_json_headers(env), &block)
  end

  def put(uri, params = {}, env = {}, &block)
    super(uri, convert_to_json(params), set_json_headers(env), &block)
  end

  def delete(uri, params = {}, env = {}, &block)
    super(uri, convert_to_json(params), set_json_headers(env), &block)
  end

  # override other HTTP methods if necessary

  private

  def set_json_headers(env={})
    env.merge({'ACCEPT' => "application/json", 'CONTENT_TYPE' => 'application/json'}) unless env.nil?
  end

  def convert_to_json(params={})
    params.to_json unless params.nil?
  end

end

Then I added the below to my spec_helper.rb

# require assuming project root is loaded into ruby's class paths
require './spec/helpers/rspec_http_request_override_helper'
RSpec.configure do |config|
  config.include RspecHttpRequestsOverrideHelper

  # Other settings
end

And that was it!

Note: The get method above doesn't convert the params value to json intentionally. Param values are encoded into the query string and then sent. Not as part of the HTTP body in the request, even though the GET http method supports sending a body; see here for more details.

My issue was that the rspec test helpers for an API I am building were converting boolean types to string types when sending the request to the API. Turns out when you don't specify a content-type header for json the data is passed as multipart/form-data or x-www-form-urlencoded depending on the HTTP method; see here for more details. This was converting my special data types, which are valid in json like integers and booleans, into strings. And effectively needing me to convert them on the API's end. It wasn't until I added validation for the input into my API that this was exposed. Yay for validations and tests!

Now, I needed to effectively apply a content-type header to all my requests and convert the params to json when sending the requests; I was calling the http methods with the params value being a ruby hash. I have over 200 tests so going in an manually changing them all would not have been an optimal solution. So I implemented the below solution. Which works very well.

I decided to follow the same method definition as rack-test was and then I could effectively call super after editing the requests.

My failing tests now started passing and my previous tests where none the wiser.

Hopefully this helps others who run into a similar issue.

Community
  • 1
  • 1
Patelify
  • 1,790
  • 1
  • 13
  • 14
-1

The question betrays incorrect assumptions. You shouldn't be writing controller specs in the first place. This should all be done with Cucumber -- and in that case, you can just specify particular URLs, so the problem goes away.

Marnen Laibow-Koser
  • 5,182
  • 1
  • 23
  • 30
  • Controller specs are a very standard thing even in conjunction with cucumber. Why do you say they're inappropriate? – Peter Nixey Dec 15 '11 at 11:41
  • I wouldn't call controller specs "standard...in conjunction with Cucumber". Most developers I know who write Cucumber stories do so as a replacement for controller specs. And that's the way it usually should be. Controller specs are inappropriate because they test implementation in a domain where user-facing behavior is what's actually important. Thus, they're needlessly painful to write and (usually) don't actually test what should be tested. Conclusion: avoid entirely, except in a few edge cases. RSpec is generally only useful at the model level. – Marnen Laibow-Koser Dec 15 '11 at 14:59
  • While I do agree that full integration testing is a more useful and powerful way to test controllers I do think RSpec is still useful. Perhaps my ideas of its usefulness are your edge cases but ensuring that the right things end up in the database, the wrong things don't, things do/don't get deleted, ensuring that access control is happening at the right level These are all things that aren't verified by cucumber. I don't think controller specs need to be long but they are useful – Peter Nixey Dec 16 '11 at 15:13
  • "These are all things that aren't verified by cucumber" -- dead wrong. Your Cucumber stories can and should be making simple checks to ensure that records get created or deleted. As for access control happening at the right level, that's an implementation concern, not a behavioral one. All your tests should care about is that the access control is *happening*, not where. – Marnen Laibow-Koser Dec 16 '11 at 16:06
  • Although I disagree with your position on some of this and this would be an interesting discussion to have in person your style of commenting is one that I find (probably unintentionally) inflammatory. For that reason and since we won't resolve these approaches in the comments I'm going to respectfully disagree and sign off. Thank you for your input. – Peter Nixey Dec 16 '11 at 17:13
  • You know, I'd actually prefer to have this discussion if it's OK with you. If we disagree, that probably means *at least* one of us is wrong. If I'm wrong, I really would like to know. If you're wrong, you really should know. So please, let's continue. – Marnen Laibow-Koser Dec 16 '11 at 17:20
  • BTW, what are you finding inflammatory? I've reread my comments with that in mind, and really don't see it. I'm certainly not pulling my punches here in terms of saying things are inappropriate, but I don't see anything that I'd consider inflammatory if it were addressed to me. – Marnen Laibow-Koser Dec 16 '11 at 17:23
  • You've got a very direct abrupt phrasing to your comments which to someone who doesn't know you doesn't seem to come across how you intend them. When communicating in writing, statements like "dead wrong" and your opening quotation of my comments gives a hostile impression. I can see that they probably weren't meant that way but that's how they come across. Rudeness like beauty can sometimes lie in the eye of the beholder though. – Peter Nixey Dec 16 '11 at 17:37
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/5918/discussion-between-peter-nixey-and-marnen-laibow-koser) – Peter Nixey Dec 16 '11 at 17:38