Burak Kaymakci
Burak Kaymakci

Reputation: 690

Rspec view test with optional locale path

I've got an optional locale parameter in my route.

# config/routes.rb
  scope '(:locale)' do
    resources :orders
    resources :line_items
    resources :carts
    root 'store#index', as: 'store_index', via: :all
  end
# config/initializers/i18n.rb
#encoding: utf-8
I18n.default_locale = :en

LANGUAGES = [
  ['English',                  'en'],
  ["Español".html_safe, 'es']
]

If I want to test my view like so,

# /spec/views/carts/edit.html.erb_spec.rb
require 'rails_helper'

RSpec.describe "carts/edit", type: :view do
  let(:cart) { create(:cart) }

  before(:each) do
    assign(:cart, cart)
  end

  it "renders the edit cart form" do
    render

    assert_select "form[action=?][method=?]", cart_path(cart), "post" do
    end
  end
end

I get an error

/Users/burak/.rvm/rubies/ruby-2.7.3/bin/ruby -I/Users/burak/.rvm/gems/ruby-2.7.3/gems/rspec-core-3.10.1/lib:/Users/burak/.rvm/gems/ruby-2.7.3/gems/rspec-support-3.10.2/lib /Users/burak/.rvm/gems/ruby-2.7.3/gems/rspec-core-3.10.1/exe/rspec spec/views/carts/edit.html.erb_spec.rb
F

Failures:

  1) carts/edit renders the edit cart form
     Failure/Error: <%= form_with(model: cart) do |form| %>

     ActionView::Template::Error:
       No route matches {:action=>"show", :controller=>"carts", :format=>nil, :locale=>#<Cart id: 1252, created_at: "2021-04-27 13:45:09.842106000 +0000", updated_at: "2021-04-27 13:45:09.842106000 +0000">}, missing required keys: [:id]
       Did you mean?  cart_url
                      carts_url
                      carts_path
                      new_cart_url
     # ./app/views/carts/_form.html.erb:1:in `_app_views_carts__form_html_erb__2203507052991398875_16560'
     # ./app/views/carts/edit.html.erb:3:in `_app_views_carts_edit_html_erb___4312857554407673387_16540'
     # ./spec/views/carts/edit.html.erb_spec.rb:11:in `block (2 levels) in <top (required)>'
     # ------------------
     # --- Caused by: ---
     # ActionController::UrlGenerationError:
     #   No route matches {:action=>"show", :controller=>"carts", :format=>nil, :locale=>#<Cart id: 1252, created_at: "2021-04-27 13:45:09.842106000 +0000", updated_at: "2021-04-27 13:45:09.842106000 +0000">}, missing required keys: [:id]
     #   Did you mean?  cart_url
     #                  carts_url
     #                  carts_path
     #                  new_cart_url
     #   ./app/views/carts/_form.html.erb:1:in `_app_views_carts__form_html_erb__2203507052991398875_16560'

Finished in 0.02277 seconds (files took 0.60597 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/views/carts/edit.html.erb_spec.rb:10 # carts/edit renders the edit cart form

So as far as I understood, that's because my cart object is being passed with a :locale keyword to somewhere. If I explicitly pass the URL to form_with in my _form partial, then the error disappears but then that breaks some other tests. How can I fix this?

Here are the necessary partials and views.

# app/views/carts/edit.html.erb
<h1>Editing Cart</h1>

<%= render 'form', cart: @cart %>

<%= link_to 'Show', @cart %> |
<%= link_to 'Back', carts_path %>
# app/views/carts/_form.html.erb
<%= form_with(model: cart) do |form| %>
  <% if cart.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(cart.errors.count, "error") %> prohibited this cart from being saved:</h2>

      <ul>
        <% cart.errors.each do |error| %>
          <li><%= error.full_message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

Upvotes: 0

Views: 945

Answers (1)

ScottM
ScottM

Reputation: 10422

So the route for the URL that your form wants to point to is (in the format you'll see from rails routes):

(/:locale)/carts/:id(.:format)

What looks to be happening is that when your view spec is rendering, it calls cart_path(cart) to build the form's action attribute. And what's happening is that helper method, cart_path, tries to populate the parameterised parts of the path from left to right, and it uses cart to try and populate the :locale segment instead of the :id segment.

You don't see this happen in the context of being rendered by a controller action in a rails server, because the controller has a concept of locale having a default value, and so it understands that cart represents the second, :id part of the path.

If you want to ensure that your views can behave similarly when tested in isolation, then you can add a default value to your routes file:

scope '(:locale)', defaults: { locale: 'en' } do
  ...

And in your config/environments/test.rb, add a further default:

Rails.application.configure do
 # ...
 routes.default_url_options[:locale] = 'en'
end

Setting the default in routes.rb will allow your view file to correctly generate a valid path; adding a default_url_options means you can write cart_path(cart) in your view spec and it will know to use 'en' as the locale by default. Without it, you'd have to assert that the action path was cart_path(cart, locale: 'en').

Upvotes: 2

Related Questions