29

This is my action:

def my_action
  str = ... # get json str somehow  
  render :json => str
end

This is my test:

test "my test" do 
  post(:my_action, {'param' => "value"}    
  assert_response :success
end

I want to add another assertion that the emitted JSON contains some value. How can I do it in a controller unit-test, not via parsing the view result?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Yaron Naveh
  • 23,560
  • 32
  • 103
  • 158
  • 1
    Wouldn't parsing the json response be the easiest way? – jdeseno Nov 26 '11 at 23:01
  • I was under the impression that unit tests do not actually invoke the view. Is it the case? If yes which kind of test is what I look for (view?) – Yaron Naveh Nov 26 '11 at 23:04
  • 1
    I believe this question was already discussed [here](http://stackoverflow.com/questions/336716/how-to-test-json-result-from-rails-functional-tests). What you're doing is not unit, but functional test. And it actually renders the view. – KL-7 Nov 26 '11 at 23:07

4 Answers4

58

Just like people commented above, this would be a functional test.

The best way would probably be making a request, parsing the JSON response body, and matching it to the expected result.

If I have companies_controller in Rspec using FactoryGirl:

describe "GET 'show'" do

  before(:each) do
    @company = Factory(:company)
    get 'show', :format => :json, :id => @company.id
  end

  it "should be successful" do
     response.should be_success
  end

  it "should return the correct company when correct id is passed" do
    body = JSON.parse(response.body)
    body["id"].should == @company.id
  end

end

You can test other attributes the same way. Also, I normally have invalid context where I would try to pass invalid parameters.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Simon Bagreev
  • 2,879
  • 1
  • 23
  • 24
  • Doesn't FactoryGirl require a model, so in your example, you have to have a model for "company"? – Ninja Oct 24 '14 at 06:13
24

Using Rails' built-in functional test:

require 'test_helper'

class ZombiesControllerTest < ActionController::TestCase

  setup do
    @request.headers['Accept'] = Mime::JSON
    @request.headers['Content-Type'] = Mime::JSON.to_s

  end

  test "should post my action" do
    post :my_action, { 'param' => "value" }, :format => "json"
    assert_response :success
    body = JSON.parse(response.body)
    assert_equal "Some returned value", body["str"]
  end

end
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
kendotwill
  • 1,892
  • 18
  • 17
  • This works when you have a respond_to function that returns different data based off the different blocks.. In the example above, the poster defines the method in which the REQUEST is sent to the controller, to trigger the right respond_to block. – FlyingV Jan 02 '17 at 19:39
6

This controller test worked well for me using Minitest with Rails 4.2.4:

require 'test_helper'

class ThingsControllerTest < ActionController::TestCase

  test "should successfully create a new thing" do
    assert_difference 'Thing.count' do
      @request.headers["Accept"] = "application/json"

      post(:create, {thing: {name: "My Thing"}})
    end

    assert_response :success

    json_response = JSON.parse(@response.body)
    assert_equal json_response["name"], "My Thing"
  end

end

And this worked in the form of an integration test.

require 'test_helper'

class ThingsRequestsTest < ActionDispatch::IntegrationTest

  test "creates new thing" do
    assert_difference 'Thing.count' do
      post("/things.json", {thing: { name: "My Thing"}})
    end

    assert_equal 201, status

    json_response = JSON.parse(@response.body)
    assert_equal json_response["name"], "My Thing"
  end

end

Honestly, it's weird trying to keep the syntactical nuances straight from one type of test to another.

joshneipp
  • 91
  • 2
  • 5
4

My approach to this is slightly different if I'm using the Jbuilder gem that is now available from the Rails team. (This approach applies to other gems that render JSON or XML as views.) I prefer unit tests over functional tests whenever possible, since they can be quite a bit faster. With Jbuilder you can transform most of the tests into unit tests.

Yes, you still have functional tests on the controller, but there are very few and they don't parse the JSON. The functional test solely test the controller logic, not the rendered JSON. A functional test for a valid request might assert the following (RSpec):

  assert_response :success
  expect(response).to render_template(:show)
  expect(assigns(:item).id).to eq(expected_item.id)

I'm just verifying that it is successful, it renders the template, and it passes the item to the template. At this point, the view has the information it needs to do the proper rendering.

Now test the JSON rendered by unit testing the Jbuilder view.

describe 'api/v1/items/show.json.jbuilder' do
  it 'includes foo' do
    assign(:item, account.build(foo: 'bar'))

    render

    json = JSON.parse(rendered)
    expect(json['item']['foo']).to eq('bar')
  end

  # A bunch of other JSON tests...
Jay Mitchell
  • 1,230
  • 11
  • 14