thoughtbot / factory_bot

A library for setting up Ruby objects as test data.
https://thoughtbot.com
MIT License
7.92k stars 2.6k forks source link

Odd behaviour with `before_validation` #1099

Closed daniel-gato closed 6 years ago

daniel-gato commented 6 years ago

Hello,

I'm new to TDD. Actually, I did the whole app without tests and now I'm adding tests and trying to learn how to do them and how to catch back to TDD.

Something seems fishy, but I can't see where the problem comes from. It works within the rails console but not when I execute my tests.

My Factory

FactoryBot.define do
  factory :venue do
    name "Bronda"
  end
end

My Test

require 'test_helper'

class VenueTest < ActiveSupport::TestCase
  test "increments slug when name exists" do
    venue_name = 'Capri'

    first_venue = create(:venue, name: venue_name)
    assert_equal 'capri', first_venue.slug

    second_venue = create(:venue, name: venue_name)
    assert_equal 'capri-2', second_venue.slug
  end
end

My Model

class Venue < ApplicationRecord
  # (...)
  validates :slug, presence: true, uniqueness: { case_sensitive: false }
  before_validation :set_slug

  def set_slug
    return nil if name.blank?

    base_slug = name.to_slug

    new_slug = if slug.blank?
      base_slug
    else
      slug
    end

    i = 1
    if slug.blank? || Venue.where(slug: new_slug).count > 1
      loop do
        break if Venue.where(slug: new_slug).count == 0
        i += 1
        new_slug = "#{base_slug}-#{i}"
      end
      self.slug = new_slug.downcase
    end
  end

  def to_param
    slug
  end
end

When I execute my tests I get this error

dextermac1:reshel-rails Daniel$ rake test --verbose
Run options: --seed 10253

# Running:

.................E

Error:
VenueTest#test_increments_slug_when_name_exists:
ActiveRecord::RecordInvalid: Validation failed: Slug has already been taken
    test/models/venue_test.rb:42:in `block in <class:VenueTest>'

/Users/Daniel/.rvm/gems/ruby-2.4.1/gems/railties-5.1.4/lib/rails/test_unit/reporter.rb:70:in `method': undefined method `test_increments_slug_when_name_exists' for class `Minitest::Result' (NameError)
    from /Users/Daniel/.rvm/gems/ruby-2.4.1/gems/railties-5.1.4/lib/rails/test_unit/reporter.rb:70:in `format_rerun_snippet'
    from /Users/Daniel/.rvm/gems/ruby-2.4.1/gems/railties-5.1.4/lib/rails/test_unit/reporter.rb:23:in `record'
    from /Users/Daniel/.rvm/gems/ruby-2.4.1/gems/minitest-5.11.1/lib/minitest.rb:786:in `block in record'
    from /Users/Daniel/.rvm/gems/ruby-2.4.1/gems/minitest-5.11.1/lib/minitest.rb:785:in `each'
    from /Users/Daniel/.rvm/gems/ruby-2.4.1/gems/minitest-5.11.1/lib/minitest.rb:785:in `record'
    from /Users/Daniel/.rvm/gems/ruby-2.4.1/gems/minitest-5.11.1/lib/minitest.rb:334:in `run_one_method'
    from /Users/Daniel/.rvm/gems/ruby-2.4.1/gems/minitest-5.11.1/lib/minitest.rb:321:in `block (2 levels) in run'
    from /Users/Daniel/.rvm/gems/ruby-2.4.1/gems/minitest-5.11.1/lib/minitest.rb:320:in `each'
    from /Users/Daniel/.rvm/gems/ruby-2.4.1/gems/minitest-5.11.1/lib/minitest.rb:320:in `block in run'
    from /Users/Daniel/.rvm/gems/ruby-2.4.1/gems/minitest-5.11.1/lib/minitest.rb:360:in `on_signal'
    from /Users/Daniel/.rvm/gems/ruby-2.4.1/gems/minitest-5.11.1/lib/minitest.rb:347:in `with_info_handler'
    from /Users/Daniel/.rvm/gems/ruby-2.4.1/gems/minitest-5.11.1/lib/minitest.rb:319:in `run'
    from /Users/Daniel/.rvm/gems/ruby-2.4.1/gems/railties-5.1.4/lib/rails/test_unit/line_filtering.rb:9:in `run'
    from /Users/Daniel/.rvm/gems/ruby-2.4.1/gems/minitest-5.11.1/lib/minitest.rb:159:in `block in __run'
    from /Users/Daniel/.rvm/gems/ruby-2.4.1/gems/minitest-5.11.1/lib/minitest.rb:159:in `map'
    from /Users/Daniel/.rvm/gems/ruby-2.4.1/gems/minitest-5.11.1/lib/minitest.rb:159:in `__run'
    from /Users/Daniel/.rvm/gems/ruby-2.4.1/gems/minitest-5.11.1/lib/minitest.rb:136:in `run'
    from /Users/Daniel/.rvm/gems/ruby-2.4.1/gems/minitest-5.11.1/lib/minitest.rb:63:in `block in autorun'
Coverage report generated for MiniTest to /Users/Daniel/GitHub/reshel-rails/coverage. 410 / 645 LOC (63.57%) covered.

2.4.1 :035 > v1 = Venue.create(name: 'capri')
   (0.2ms)  BEGIN
   (1.8ms)  SELECT COUNT(*) FROM "venues" WHERE "venues"."slug" = $1  [["slug", "capri"]]
  Venue Exists (1.1ms)  SELECT  1 AS one FROM "venues" WHERE LOWER("venues"."slug") = LOWER($1) LIMIT $2  [["slug", "capri"], ["LIMIT", 1]]
  SQL (8.6ms)  INSERT INTO "venues" ("status", "name", "created_at", "updated_at", "slug") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["status", 0], ["name", "capri"], ["created_at", "2018-01-15 20:41:10.770194"], ["updated_at", "2018-01-15 20:41:10.770194"], ["slug", "capri"]]
  Venue Exists (1.9ms)  SELECT  1 AS one FROM "venues" WHERE LOWER("venues"."slug") = LOWER($1) AND ("venues"."id" != $2) LIMIT $3  [["slug", "capri"], ["id", 359], ["LIMIT", 1]]
   (9.3ms)  COMMIT
 => #<Venue id: 359, status: "editing", name: "capri", email: nil, phone: nil, route: nil, locality: nil, postal_code: nil, note: nil, facebook: nil, twitter: nil, instagram: nil, region_id: nil, group_id: nil, created_at: "2018-01-15 20:41:10", updated_at: "2018-01-15 20:41:10", slug: "capri", street_number: nil, floor: nil, country: "Finland", aggregated_reviews_count: nil, aggregated_rating: nil, aggregated_price_level: nil> 
2.4.1 :036 > v2 = Venue.create(name: 'capri')
   (0.3ms)  BEGIN
   (1.2ms)  SELECT COUNT(*) FROM "venues" WHERE "venues"."slug" = $1  [["slug", "capri"]]
   (1.3ms)  SELECT COUNT(*) FROM "venues" WHERE "venues"."slug" = $1  [["slug", "capri-2"]]
  Venue Exists (1.5ms)  SELECT  1 AS one FROM "venues" WHERE LOWER("venues"."slug") = LOWER($1) LIMIT $2  [["slug", "capri-2"], ["LIMIT", 1]]
  SQL (2.3ms)  INSERT INTO "venues" ("status", "name", "created_at", "updated_at", "slug") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["status", 0], ["name", "capri"], ["created_at", "2018-01-15 20:41:20.610988"], ["updated_at", "2018-01-15 20:41:20.610988"], ["slug", "capri-2"]]
  Venue Exists (0.9ms)  SELECT  1 AS one FROM "venues" WHERE LOWER("venues"."slug") = LOWER($1) AND ("venues"."id" != $2) LIMIT $3  [["slug", "capri-2"], ["id", 360], ["LIMIT", 1]]
   (0.5ms)  COMMIT
 => #<Venue id: 360, status: "editing", name: "capri", email: nil, phone: nil, route: nil, locality: nil, postal_code: nil, note: nil, facebook: nil, twitter: nil, instagram: nil, region_id: nil, group_id: nil, created_at: "2018-01-15 20:41:20", updated_at: "2018-01-15 20:41:20", slug: "capri-2", street_number: nil, floor: nil, country: "Finland", aggregated_reviews_count: nil, aggregated_rating: nil, aggregated_price_level: nil> 
2.4.1 :037 > v1.slug
 => "capri" 
2.4.1 :038 > v2.slug
 => "capri-2" 

As you can see, when I run it through the rails console, I get both venues created with the corresponding slug.

Any idea where I'm going wrong here?

daniel-gato commented 6 years ago

... I just got what was wrong. Problem with the downcase. Should have implemented tests to see this coming 😄