atmos.org

getting better at software

Merb 1.0 Controller Testing

Posted on | November 29, 2008 | 15 Comments

This was originally going to be part of “testing flatirons” but it got kinda long and I felt it’d be most useful to community if I made it a standalone article. I’ve had the pleasure of experimenting with everything I cover in this write-up, and the state of merb’s testing environment is getting better. Since merb is at 1.0 these days this rundown should be valid for at least a few more months. I’m gonna run through what I know about how you test in merb, and in the next few days I’ll dissect flatirons. Here’s what I’ll be covering in this write up:

    Request

  • dispatch_to
  • get/post/put/delete
  • request
    Requests with Authentication

  • dispatch_to / http verb tests
  • requests w/ given blocks
    RSpec Matchers FTW

  • have_selector
  • have_xpath
  • Roll Your Own

1.1 dispatch_to

This method allows you to unit test controllers. It also makes your co-workers cry, swear to kill you, and rewrite your code.

It seems harmless enough. dispatch_to takes 4 parameters and an optional block. The 3rd parameter is the actual params hash that the action will see. The 4th is your http environment and how you modify things like HTTP_ACCEPT to change content type. When you call this method it instantiates the Class and calls the Action with your http params, you bypass routing and have the option to stub/mock to your heart’s content. The response from dispatch_to returns an instance of the class, in this case it’s the Sessions controller. The response also has a few instance methods that might be useful to check in your testing:

  • #status – the http response code
  • #body – the document returned from the action
  • #headers – the http headers returned
  • #session – the session for the request

In my experience dispatch_to leads to really brittle tests when things start to get a little more complex. While I’m a HUGE fan of rr, I’m finding that mocks are mostly complicating my day to day work. I wanna see my code work from beginning to end, even if it means my test suite takes a little longer. The merb-core team advises against using dispatch_to. If you’re going to be using merb for the foreseeable future, do not get in the habit of using this. Seriously.

1.2 get/post/put/delete

The standard HTTP verbs are available as request helpers in your test environment too. It’s kinda like dispatch_to but it actually goes through the router. Insted of giving a Class and an Action to call, you give the same path you would request in a browser. The optional 2nd and 3rd parameters to this function are the request parameters and http environment respectively. This also has a block syntax that you’ll see later in this tutorial. Here’s what the above request looks like using the HTTP verb helpers.

The response from get / post / put / delete has the same instance methods available that dispatch_to has since they both return the actual controller that was executed.

This is cooler than dispatch_to because it goes through the router and we can make sure that our pretty “/login” url is properly configured in the router.

1.3 request

request is short and sweet. It’s a programmatic browser experience of sorts. At first glance it doesn’t seem much better than get but it actually preserves states between a series of requests. We’ll see why this kicks ass later in the tutorial. Here’s how you display the login form with the request helper.

The response from request is a struct that provides the following instance methods. In general I try not to call these instance methods, it tends to be more clean when you write matchers against the response object itself.

  • #status – the http response code
  • #body – the document returned from the action
  • #headers – the http headers returned
  • #url – the url that was requested
  • #original_env – i think this is how stuff persists between requests. I’ve never actually called this for anything.

2.0 Testing with Authentication

2.1 merb-auth and all that jazz

So you’re supposed to be using merb-auth if you need authentication in your app. So how do you test it?

2.2 dispatch_to and HTTP verbs with Authentication

Both dispatch_to and get can take an optional block that allows you to spec and mock things on the controller. If you need to stub out authentication both helpers are easily spec’d like this.

While adding the mocks and stubs might yield some immediate results, it tends to be a maintenance nightmare. You either end up with a bunch of duplicated stuff in each spec or you end up wrapping these methods with things like employee_get/employee_put etc.

Even with this bit of abstraction it becomes a royal PITA to test real use cases. Since each spec tests the controller action in isolation you open yourself up to the possibility of having bugs in your tests that are directly related to keeping your mocks/specs synced up with your real code. “Does the signup process really work from start to finish?” “I don’t really know what that chunk of code does, but I know it returns true or false.” It becomes very easy to bypass chunks of code in certain situations without really understanding how things integrate. The request helper shines bright in this situation.

2.3 requests with given blocks

The request helper allows you to preserve the normal browser experience by making successive calls to request and preserving the user session. So here’s how you authenticate against merb-auth:

It sucks that you had to explicitly call that request to “/login” but it’s kinda nice to be able to comprehend the user experience that led up to the point where the code in question was tested. Someone logged in and then they could view their account settings.

Either Merb or RSpec provide you with this given block to setup pre conditions that the spec relies on. They’re kinda like shared_examples but if you’re using request you can build entire use cases up and attach them to describe blocks. Your session persists between requests, instant WIN! I added this to the bottom of my spec/spec_helper.rb to allow for authenticated requests.

After that your test code starts to look like this

If you’re super awesome you can pass “cookie jars” along with your requests to restore certain states. I think they’ll be really useful in situations where specs start taking forever because of the amount of setup required to get to a certain state. Thus far our specs aren’t taking a long time so I guess I’ll cross this bridge when our spec suite starts taking forever.

One other thing to keep in mind is that your HTTP_HOST environmental variable is http://example.org. So don’t be surprised if you see this popping up in your tests.

3. RSpec Matchers

Remember how both the dispatch_to and request responses have a few instance methods that are useful in testing? Notice that we didn’t explicitly call them in any of the spec examples above? It’s because we’re using the rspec matchers that merb provides. Not only should you be using the ones that merb provides, you should be writing custom ones for your app. Here’s the matchers that I use from merb on a daily basis.

3.1 HTTP Response Checks

  • be_successful This basically checks that response.status was 200. It was a successful request. :D
  • redirect This checks that response.status was 302. It’s your normal HTTP redirect code.
  • redirect_to This not only checks that response.status was 302 but it takes a string for the path portion of the redirection. If you need to check query parameters you’re going to have to write your own matcher. We have one in one of our apps now, redirect_to_with_params that does exactly this, we’re hoping to give it back to the merb community shortly.

Markup Validation

have_selector is what you should be using to validate HTML markup. It allows you to specify to identify the presence of certain markup. If you’re into that thing, it’s great practice for selectors in js too. Let’s say we want to check for the presence of the ‘Hello World’ string in the following example.

In your spec you’d have something like the following:

It’s pretty straightforward, find the div with an h2 that has the dom id of ‘hello’ and contains the string ‘Hello World’. Now is a good time to review the example specs above, hopefully you can visualize the markup that those specs require now.

have_xpath is the slightly older cousin of have_selector. Instead of CSS 3 Selectors you specify an xpath that should be present in the response document. I’ve gotten in the habit of only using this for XML documents. Let’s see the Hello World example done with xpath.

3.3 Roll Your Own

Rolling your own RSpec matchers is the super pimp stuff. You can cut down on duplication in your tests by coming up with an expressive name for certain situations, their responses, and the markup that’s returned. Here’s a little matcher we wrote in flatirons that handles unauthenticated requests returned by merb-auth.

We put this matcher into our spec/spec_helper.rb file and included it into our rspec environment with the following code.

So that’s about it for the merb controller testing intro. Now that we’ve discussed the basics of testing we can get back into flatirons and talk about how things are tested in that app.

I’m sure I made some errors in here, please offer up corrections in the comments and I’ll try to work the fixes into the article soonish.

Comments

15 Responses to “Merb 1.0 Controller Testing”

  1. Ahsan Ali
    November 30th, 2008 @ 12:50 am

    Great article ! I’ve been using request() and loving it.

    I’ve been using shared examples, but given seems cleaner. I looked at the merb source and ‘given’ seems to be a convenience wrapper around the ’shared’ functionality provided by rspec.

  2. Paul Barry
    November 30th, 2008 @ 6:43 am

    Hey Corey,

    Great post, I’ve gotten in to trouble before using mocking and specing in the controllers, this looks like a great alternative.

  3. Dale Campbell
    November 30th, 2008 @ 9:37 am

    Good read. As advocated by the Merb core team, and suggested by a few people in #merb, it’s a good thing to test the full stack. Here’s a couple of spec helper methods that I use for testing an authenticated controller/action.

  4. Dale Campbell
    November 30th, 2008 @ 9:37 am
  5. The Merbist » Blog Archive » latest news from Merbland (Nov 30)
    November 30th, 2008 @ 4:35 pm

    [...] Testing your controller from atmos.org [...]

  6. Paul
    November 30th, 2008 @ 6:27 pm

    Nice post, Corey!

    I’ve taken this a step further, and abstracted all my web service specs and written some (IMO) clever helpers to make it easier. Here’s an example of a spec that creates a record from a JSON POST:

    http://gist.github.com/29237

    I need to write up a blog post on how I did this. I wrote helpers for both Rspec Groups and Examples.

    This made LOC in my web service specs go down from 100+ to ~20.

  7. Antares Trader
    December 1st, 2008 @ 12:31 pm

    Is it possible to have multiple :given’s in a spec? if so, how?

  8. atmos
    December 1st, 2008 @ 7:31 pm

    Hey Antares,

    Right now you can’t pass multiple givens, but I’ve been told that they’re planned for the future. Perhaps you could add them? :)

  9. Scott Motte » Blog Archive » My favorite merb guides, tutorials, and resources
    December 14th, 2008 @ 5:13 pm

    [...] Merb controller testing [...]

  10. til
    December 18th, 2008 @ 8:30 am

    Helpful post, thanks!

    Do you have an idea how to extend the custom matcher to support should_not as well? Since the have_selector matcher throws an exception when it doesn’t match, but rspec wants matches? to return true or false, I ended up coercing it into returning a boolean by rescuing Spec::Expectations::ExpectationNotMetError and returning false when the exception is thrown, but it doesn’t look too pretty.

  11. atmos
    December 18th, 2008 @ 9:36 am

    @til

    Are you SURE your selector in have_selector is valid? I was able to test should_not with have_selector in flatirons, it seems to be behaving as expected(true or false result). Here’s a link to a patch that I just tested it with, https://gist.github.com/a37d76730d6b6d4808ad

  12. til
    December 19th, 2008 @ 7:48 pm

    Sorry, I wasn’t very clear. What I meant to ask was how to make the custom matcher support being called with a should_not instead of a should. Following your example I’d want to be able to add this in a spec:

    response.should_not be_a_valid_merb_auth_form

    Here is what I did: http://gist.github.com/38214 – I would appreciate a more elegant way.

    I think that your example

    def matches?
    response.should_not have_selector(”blink”)
    end

    does not behave as intended because the should_not expression returns false, yet you want matches? to return true.

  13. Kris
    February 19th, 2009 @ 12:11 pm

    Hi, I know this is an old post, but it’s a good one and I was wondering if you knew where I could learn more about how to use “cookie jars” as you mention? I have some request/controller tests that I’d like to setup with cookie values and I’m unclear as to the best way of doing this.

    Thank you for the excellent article. It really helped in how I put my specs together.

    Bests,

    Kris

  14. scottmotte
    March 11th, 2009 @ 11:21 pm

    atmos, how would you go about changing the HTTP_HOST environmental variable? I’m trying to spoof a subdomain.

  15. atmos
    March 19th, 2009 @ 5:32 pm

    Hey Scott,

    You basically want to pass that in as a third parameter, something like this. http://gist.github.com/82146

Leave a Reply