I'm building a JSON API. I'm writing my controller tests so that they compare the response body to actual JSON I'm generating in my tests using ActiveRecord. I wrote two helper methods that make the process easier. The first simply parses the actual response body and returns it.
def json_response_body(&block)
JSON.parse(response.body, symbolize_names: true).tap do |content|
yield content if block_given?
end
end
The second method returns a JSON representation of an object, and accepts options. This is what I expect the response to be. So I compare the output of this method to the actual response.
def as_parsed_json(object, options = {})
# Converting to JSON and reparsing ensures
# dates are in the same format as my controller responses.
JSON.parse object.to_json(options), symbolize_names: true
end
This way, I can write my tests like this:
test "GET #show" do
product = create(:product)
get :show, id: product
expected =
as_parsed_json(product, only: [:id, :title, :price_in_cents])
assert_equal expected, json_response_body
assert_response :success
end
Is this a good way of testing me controllers? The part that makes me think is where I'm generating the JSON using Rails helpers instead of explicitly writing the JSON myself, or looking for keys in the response body. For example:
expected = { id: product.id, title: product.title }.to_json
# Or
assert_equal product.id, request.body[:id]
But since Rails is pretty well tested, I feel like I can trust its JSON return. I'm not sure if this approach makes my tests brittle, since any change in the response body will cause the tests to fail. But maybe that's a good thing? Here's another example:
test "POST #create with valid attributes" do
user = create(:user)
api_authorization_header token: user.auth_token
post :create, product: attributes_for(:product)
expected = as_parsed_json Product.last, json_options
assert_equal expected, json_response_body
assert_response :created
end
And it's controller action:
# app/controllers/api/v1/users_controller.rb
def create
@user = User.new
@user.assign_attributes permitted_attributes(@user)
if @user.save
render :create, status: 201, location: [:api, @user]
else
render json: { errors: @user.errors }, status: 422
end
end
I'm using Jbuilder to render the actual JSON.
# app/views/api/v1/users/create.json.jbuilder
json.(@user, :id, :name, :email, :created_at)
json.auth_token @user.auth_token