0
votes

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?

2

2 Answers

0
votes

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 }
0
votes

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.