Search code examples
ruby-on-railsrubycapybaraminitest

How to fix this sort_by in collection_select works in the browser but returns "comparison of String with nil failed" error in Capybara


I've been stuck on trying to write a system test for a form that manually works in the browser.

I have a Speaker model that has_one :individual. The form field looks like this:

<fieldset>
  <%= form.label :speaker, "Who said it?" %>
  <%= form.collection_select :speaker_id, Speaker.all.sort_by(&:full_name), :id, :full_name %>
</fieldset>

The custom full_name method in the Speaker model looks like this:

  def full_name
    if individual
      "#{individual.first_name} #{individual.last_name}"
    end
  end

The if individual conditional is a smell, but without it, I get this error:

Minitest::UnexpectedError: ActionView::Template::Error: undefined method `first_name' for nil:NilClass

If I add the if individual conditional then the error looks like this:

Minitest::UnexpectedError: ActionView::Template::Error: comparison of String with nil failed

I'm confused because I'm using a similar pattern to sort collection selects by a full_name in other models, but this is the only one that raises an error in the system test. Also everything works as expected when I manually test in the browser.

I can also access the the first_name last_name and full_name methods in the Individual model in the console, no problem.

This also doesn't cause an error (and I can use :full_name to display the name in the browser, but it's just not alphabetical:

<%= form.collection_select :speaker_id, Speaker.all, :id, :full_name %>

enter image description here

How can I fix this error in Capybara? Or is there a better way to return Speaker.all in the collection_select sorted alphabetically?


Update: I simplified this by not passing the Quote / Individual relationship through a Speaker model. Now, my Quote model belongs_to :individual and Individual has_many :quotes. This is also working in the browser, but I'm getting the same error with my system test:

<ActionView::Template::Error: undefined method `full_name' for nil:NilClass>

Here's one of the tests that is failing with this error...

require "application_system_test_case"

class QuotesTest < ApplicationSystemTestCase
  setup do
    @contributor = users(:contributor)
    @contributor_quote = quotes(:contributor_quote)
    @category = categories(:rock_and_roll)
    @field = fields(:music)
    @topic_one = topics(:recording)
    @topic_two = topics(:songwriting)
    @individual = individuals(:john_lennon)
    @group      = groups(:the_beatles)
  end

  test "visiting the index should be accessible to all" do
    visit quotes_path
    assert_text @contributor_quote.body
    click_on @contributor_quote.body
    assert_current_path quote_path(@contributor_quote)
  end
end

My Individual model has this method in it:

def full_name
  "#{first_name} #{last_name}"
end

And this is the part of the view that is causing the failure:

  Speaker: <%= @quote.individual.full_name %>

If I include save_and_open_page the error looks like this...

enter image description here

I tried adding .strip as Thomas' answer suggested, but that's not helping.

Again, the only problem is this system test. It works fine in the browser.


Solution

  • Without actually seeing any of the test code the error is coming from it's impossible to know for sure, and I have no idea where Capybara is fitting into this. I'm guessing that what you have is a Speaker model that seems to depend on there being an individual associated with it, but that's not actually enforced anywhere so when you create the speaker instance in your test without an associated individual it's not being flagged as invalid. If Speaker is supposed to support there not being an associated individual then your issue is that your full_name method is returning nil when a string is required. To fix that you could do

    def full_name
      "#{individual&.first_name} #{individual&.last_name}".strip
    end
    

    to give you the full name when individual does exist, and an empty string when it doesn't