Reputation: 988
I'm working through the book Agile Web Development with Rails 6, but instead of Minitest and fixtures I use RSpec and FactoryBot.
I have two request tests that fail for the products
controller, but I don't understand why. Here's the code:
Tests
describe ProductsController, type: :request do
let(:valid_product) { build(:product) }
let(:invalid_product) { { 'foo' => 'bar' } }
before do
run_request
end
describe 'POST /create' do
context 'POST data is valid' do
let(:run_request) { post '/products', :params => { 'product' => valid_product.as_json } }
it 'creates a product' do
expect(response).to have_http_status(:created)
end
end
context 'POST data is invalid' do
let(:run_request) { post '/products', :params => { 'product' => invalid_product } }
it 'shows validations errors' do
expect(response).to have_http_status(:unprocessable_entity)
end
end
end
end
Model
class Product < ApplicationRecord
validates :title, :description, :image_url, presence: true
validates :title, uniqueness: true
validates :image_url, allow_blank: true, format: {
with: %r{\.(gif|jpg|png)\z}i,
message: 'must be a URL for GIF, JPG or PNG image.'
}
validates :price, numericality: { greater_than_or_equal_to: 0.01 }
end
create method
def create
@product = Product.new(product_params)
respond_to do |format|
if @product.save
format.html { redirect_to @product, notice: 'Product was successfully created.' }
format.json { render :show, status: :created, location: @product }
else
format.html { render :new }
format.json { render json: @product.errors, status: :unprocessable_entity }
end
end
end
I expect the valid and invalid requests to return a 201 and 422 status, respectively. Instead, I get 302 and 200. What's the problem?
Upvotes: 0
Views: 79
Reputation: 4126
In your test you are calling post '/products'
, which by default uses HTML format.
Your controller responds to a successful save (in HTML) by redirecting to the product page, which results in a 302 (:found
). In the case of a failed save, you re-render the :new
page, which is a 200 (:ok
). It sounds like you were wanting to test the JSON format results. To do that, post /products.json
or post /products, format: 'json'
. Since your controller can handle multiple response types (HTML / JSON), it would be good practice to include specs for both. Something akin to:
describe 'POST /create' do
context 'when json request' do
context 'when valid' do
...
end
context 'when invalid' do
...
end
end
context 'when html request' do
...
end
end
For the JSON format, you'd also typically also add specs to check the return payload (e.g., did it return the new product data on success, etc.) in addition to checking the HTTP response code.
Upvotes: 0
Reputation: 352
For both requests format is html.
For success example you have got 302, because you defined there redirect_to @product, notice: 'Product was successfully created.'
3xx - it is redirect status. All correct.
For fail example you received 200. It is correct also, because you have there render :new
. In this case in app view you should receive form with validations errors . It is status 200.
Your spec should be fine in case request with json content type. You need to add headers for spec request.
let(:headers) { { 'CONTENT-TYPE' => 'application/json' } }
let(:run_request) { post '/products', params: { 'product' => valid_product.as_json }, headers: headers }
or more simple way, will enough to define as
option
let(:run_request) { post '/products', params: { 'product' => valid_product.as_json }, as: :json }
Upvotes: 0