I just created a simple model and started to write unit tests for it
(I've cleaned out extraneous stuff).
The migration is:
class CreateMembers < ActiveRecord::Migration
def self.up
create_table :members do |t|
t.column :name, :string
t.column :number, :string
end
end
def self.down
drop_table :members
end
end
The model is:
class Member < ActiveRecord::Base
validates_uniqueness_of :number
end
The testing fixture is:
numeric_only_member:
id: 1
name: John Smith
number: 15171914
You might need:
number: "15171914"
So that YAML keeps this a string and not an integer. You're not using members(:numeric_only_member).number so it might not matter (the database should be a string regardless).
alpha_numeric_member:
id: 2
name: Joanne Jones
number: wke1234
So if I write a little test case (yes I know hard coded values are
fragile):
require File.dirname(__FILE__) + '/../test_helper'
require 'member'
You shouldn't need this as Rails will auto-load it when Member is first used.
class ExampleTest < Test::Unit::TestCase
fixtures :members
def test_uniqueness_of_member
dup_member = Member.new(:name => 'duplicate', :number =>
'15171914')
assert !dup_member.save
assert_equal 1, dup_member.errors.count
assert_equal "has already been taken",
dup_member.errors.on(:number)
end
end
Problem:
"assert_equal 1, dup_member.errors.count" will fail with:
test_uniqueness_of_member(SimpleTest) [test/unit/simple_test.rb:11]:
<1> expected but was <2>.
The following line would also fail with:
test_uniqueness_of_member(SimpleTest) [test/unit/simple_test.rb:12]:
<"has already been taken"> expected but was
<["has already been taken", "has already been taken"]>.
Solution:
If I remove " require 'member' " from the test case then everything
passes. I have no idea why. (Yes I'm a newbie, please don't laugh and
point). I'm digging through ActiveRecord, UnitTest, and Ruby docs and
source but haven't found an answer yet. Any ideas?
Thanks...qb
I *think* that Member has already been defined when your 'require "member"' is executed and since the idempotency of 'require' is based on the argument (i.e., require 'foo' and require './foo' will load foo.rb from the current directory twice), the explicit require re-opens the Member class and hits the validates_uniqueness_of a second time. In addition to removing your "require 'member'", I'd also suggest writing your test like this:
class ExampleTest < Test::Unit::TestCase
fixtures :members
def test_uniqueness_of_member
dup_member = Member.new(:name => 'duplicate',
:number => members(:numeric_only_member).number)
assert ! dup_member.valid?
assert dup_member.errors[:number]
assert_match /has already been taken/, dup_member.errors.on(:number)
end
end
You want to know that the record isn't valid, there's no need to actually try to save it.
You should check that the field has an error message, rather than the total error count.
I tend to test for things like error message text with regexps that will allow some flexibility in the actual value. (e.g., "has already been taken. Please choose another and try again.")
However, all you're doing is testing that the validates_uniqueness_of works. Don't you think that the Rails team already does that? You could make a case for verifying that something like a custom format is behaving correctly or that you're using the :scope option as you expect, but unless you're just practicing and convincing yourself how things work, I'd not normally test the things that Rails does.
-Rob
Rob Biedenharn http://agileconsultingllc.com
Rob@AgileConsultingLLC.com