1
votes

I'm building some test for user account creation, and am running into an issue with one of my tests. The test is supposed to verify that the username cannot be used more than one time, and for some reason the test is failing, even though the behavior is working as expected in the actual application itself.

What's strange is I have a similar test set up to prevent duplicate email addresses, and that test is actually working as expected. Since they're set up very similarly, I'm surprised one is working but the other is not.

I'm posting the entire test file, as well as the model and migration files, below. The specific test that's failing is the fifth test, "test duplicate username." If there are any other files I can provide that may help in troubleshooting this issue.

RSpec Test File:

require 'rails_helper'

RSpec.describe User, type: :model do
  # initiate new users
  let(:user) {User.new}
  let(:user1) {User.new}

  # test new account without username
  it "is not valid without a username" do
    expect(user).not_to be_valid
  end

  # test username min length
  it "must have at least 5 chars in username" do
    user.username = 'a' * 4
    expect(user).not_to be_valid
  end

  # test username max length
  it "must have fewer than 25 chars in username" do
    user.username = 'a' * 26
    expect(user).not_to be_valid
  end

  # test proper username length
  it "must have between 5-25 chars in username" do
    user.username = 'a' * 6
    expect(user).to be_valid
  end

  # test duplicate usernames
    it "cannot use the same username multiple times" do
        user.username = 'aBCDEF'
        user1.username = 'aBCDEF'
        expect(user1).not_to be_valid
    end

  # test password min length
    it "must have at least 6 chars in password" do
        user.password = 'a' * 5
        expect(user).not_to be_valid
    end

    #test password max length
    it "must have no more than 25 chars in password" do
        user.password = 'a' * 26
        expect(user).not_to be_valid
  end

  # test multiple email addresses
    it "cannot use the same email multiple times" do
        user.email = '[email protected]'
        user1.email = '[email protected]'
        expect(user1).not_to be_valid
    end

end

User Model:

class User < ApplicationRecord

  USERNAME_LENGTH = (5..25)
  PASSWORD_LENGTH = (6..25)

  validates_presence_of :username
  validates :username, length: USERNAME_LENGTH, uniqueness: true 
  validates :password, length: PASSWORD_LENGTH, allow_nil: true
  validates :email, uniqueness: true

  has_many :posts
  has_one :cart

  attr_reader :password

  def self.find_from_credentials(username, password)
    user = find_by(username: username)
    return nil unless user
    user if user.is_password?(password)
  end

  def is_password?(password_attempt)
    BCrypt::Password.new(password_digest).is_password?(password_attempt)
  end

  def password=(password)
    @password = password
    self.password_digest = BCrypt::Password.create(password)
  end
end

User Migration File:

class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :username
      t.string :session_token
      t.string :password_digest
      t.string :name
      t.string :email
      t.boolean :admin

      t.timestamps
    end

    add_index :users, :username
    add_index :users, :session_token

  end
end
1
If you save first your first user, it'd make the test pass, but this'd lead us on asking why isn't needed to save the first user on testing the email uniqueness to make the test pass.Sebastian Palma
if you see the test.log you can see the uniqueness query. uniqueness always will be checked with already existing records in databasepraga2050

1 Answers

2
votes

Your test fails specifically because you've added a validation on the presence of the username, and in that example, you're not adding emails nor for user or user1.

See:

it "cannot use the same email multiple times" do
    user.email = '[email protected]'
    user1.email = '[email protected]'
    expect(user1).not_to be_valid
end

There if you check the validity of user1 before the expectation you'll see

user1.valid?          # false
user1.errors.messages # {:username=>["can't be blank"]}

So it doesn't fail because of the uniqueness validation, but for the absence of an username.

Be sure on saving your records before testing them. In both cases Rails will check in the database if there's a record with the same attribute and give you the boolean result of the operation.

Let's move to the rails console and check with persisted data:

Suppose you have an user:

User.where(username: 'foo')
# [#<User:0x00007ff65cff14f0 id: 1, username: "foo", email: "[email protected]", ...]

Now do the attempt to create a new one, with the same username:

bar = User.new(username: 'foo')
bar.valid? # false
# User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" IS NULL LIMIT ?  [["LIMIT", 1]]
# User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE "users"."username" = ? LIMIT ?  [["username", "foo"], ["LIMIT", 1]]
bar.errors.messages # {:username=>["has already been taken"]}

Now the same but without records with username "foo":

User.where(username: 'foo')
# []
bar.valid? # true
User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" IS NULL LIMIT ?  [["LIMIT", 1]]
User Exists (0.1ms)  SELECT  1 AS one FROM "users" WHERE "users"."username" = ? LIMIT ?  [["username", "foo"], ["LIMIT", 1]]

So, you need to save your first user everytime you're checking for uniqueness to have something which to compare with.

This might help as an example:

require 'rails_helper'

RSpec.describe User, type: :model do
  let(:user) { User.new(email: '[email protected]', username: 'foo') }
  before { user.save }

  context 'username uniqueness' do
    let(:user1) { User.new(email: '[email protected]', username: 'foo') }

    it { expect(user1).to_not be_valid }
  end

  context 'email uniqueness' do
    let(:user1) { User.new(email: '[email protected]', username: 'bar') }

    it { expect(user1).to_not be_valid }
  end
end