views:

1630

answers:

5

Given a controller method like:

def show
    @model = Model.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => model }
    end
  end

What's the best way to write an integration test that asserts that the return has the expected XML?

A: 

Set the request objects accept header:

@request.accept = 'text/xml' # or 'application/xml' I forget which

Then you can assert the response body is equal to what you were expecting

assert_equal '<some>xml</some>', @response.body
Michael Sepcot
That part I know, looking for a A..Z integration test
Patrick Ritchie
That assert_equal is also very fragile. There's no guarantee of element or attribute order; if it changes, your test will break. Literal string comparison is not the right way to check the equality of XML trees.
bjnord
+4  A: 

This is the idiomatic way of testing the xml response from a controller.

class ProductsControllerTest < ActionController::TestCase
  def test_should_get_index_formatted_for_xml
    @request.env[‘HTTP_ACCEPT’] = ‘application/xml’
    get :index
    assert_response :success
  end
end
btandyco
That's a functional test, which I already have. Looking for an integration test.
Patrick Ritchie
+5  A: 

A combination of using the format and assert_select in an integration test works great:

class ProductsTest < ActionController::IntegrationTest
  def test_contents_of_xml
    get '/index/1.xml'
    assert_select 'product name', /widget/
  end
end

For more details check out assert_select in the Rails docs.

ntalbott
+1  A: 

These 2 answers are great, except that my results include the datetime fields, which are gong to be different in most circumstances, so the assert_equal fails. It appears that I will need to process the @response.body using an XML parser, and then compare the individual fields, the number of elements, etc. Or is there an easier way?

Yes; date and time fields are another reason why assert_equal is the wrong way to compare XML trees. For myself, I have not compared the whole object for equality in my tests; I've used assert_select (as ntalbott showed above) for the specific attributes I want to check.
bjnord
A: 

The answer from ntalbott shows a get action. The post action is a little trickier; if you want to send the new object as an XML message, and have the XML attributes show up in the params hash in the controller, you have to get the headers right. Here's an example (Rails 2.3.x):

class TruckTest < ActionController::IntegrationTest
  def test_new_truck
    paint_color = 'blue'
    fuzzy_dice_count = 2
    truck = Truck.new({:paint_color => paint_color, :fuzzy_dice_count => fuzzy_dice_count})
    @headers ||= {}
    @headers['HTTP_ACCEPT'] = @headers['CONTENT_TYPE'] = 'application/xml'
    post '/trucks.xml', truck.to_xml, @headers
    #puts @response.body
    assert_select 'truck>paint_color', paint_color
    assert_select 'truck>fuzzy_dice_count', fuzzy_dice_count.to_s
  end
end

You can see here that the 2nd argument to post doesn't have to be a parameters hash; it can be a string (containing XML), if the headers are right. The 3rd argument, @headers, is the part that took me a lot of research to figure out.

(Note also the use of to_s when comparing an integer value in assert_select.)

bjnord