1

I have some strange problem with rspec and rails. So I have model test:

require 'spec_helper'

describe User do
  before(:each) do
    @attr = { name: "johnkowalski", fullname: "John Kowalski", 
      email: "[email protected]", password: "foobar" }
  end

  describe "Create a user" do

    it "test1" do
      User.create!(@attr)
    end

    it "test2" do
      User.create!(@attr)
    end

    it "test3" do
      User.create!(@attr)
      @p = { name: "testowy", fullname: "John Kowalski", 
      email: "[email protected]", password: "foobar" }
      a = User.new(@p)
      a.should_not be_valid
    end

    it "test4" do
      User.create!(@attr)
    end
  end
end

It passes without a problem but when I add some integration test like:

require 'spec_helper'

describe "Users" do
  describe "signup" do
    describe "failure" do
      it "should not make a new user" do
        lambda do
          visit signup_path
          fill_in "Name", with: ""
          fill_in "Full name", with: ""
          fill_in "Email", with: ""
          fill_in "Password", with: ""
          click_button
          response.should render_template("users/new")
          response.should have_selector("div#error_explanation")
        end.should_not change(User, :count)
      end
    end
  end
end

I have failures:

Failures:

  1) Users signup failure should not make a new user
     Failure/Error: visit signup_path
     AbstractController::ActionNotFound:
       The action 'new' could not be found for UsersController
     # ./spec/requests/users_spec.rb:8:in `block (5 levels) in <top (required)>'
     # ./spec/requests/users_spec.rb:7:in `block (4 levels) in <top (required)>'

  2) User Create a user test4
     Failure/Error: User.create!(@attr)
     ActiveRecord::RecordInvalid:
       Validation failed: Email has already been taken
     # ./spec/models/user_spec.rb:28:in `block (3 levels) in <top (required)>'

Finished in 0.77216 seconds
5 examples, 2 failures

First failure is obvious (I have empty controller) but why second happen? Also if I make an integration test that passes, the model test also passes. I use rails 3.1.0.rc5 and rspec 2.6.4. Even when I comment the line a.should_not be_valid it also works. I don't understand it at all.

EDIT: I know that's validation problem but why test4 works in this example:

require 'spec_helper'

describe User do
  before(:each) do
    @attr = { name: "johnkowalski", fullname: "John Kowalski", 
      email: "[email protected]", password: "foobar" }
  end

  describe "Create a user" do

    it "test1" do
      User.create!(@attr)
    end

    it "test2" do
      User.create!(@attr)
    end

    it "test3" do
      User.create!(@attr)
      @p = { name: "testowy", fullname: "John Kowalski", 
      email: "[email protected]", password: "foobar" }
      a = User.new(@p)
      a.should_not be_valid
    end

    it "test4" do
      User.count.should == 0
    end
    it "test5" do
      User.create!(@attr)
    end
  end
end

user.rb :

class User < ActiveRecord::Base
  has_secure_password

  email_regex = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :name, presence: true, length: { maximum: 20 },
            uniqueness: { case_sensitive: false }
  validates :fullname, presence: true, length: { maximum: 30 }
  validates :email, format: { with: email_regex },
            uniqueness: { case_sensitive: false }, length: { maximum: 30 }
  validates :password, length: { in: 5..25 }

end

I found why these things happen. So when I run only model tests, I have something like that in logs:

 (0.0ms)  RELEASE SAVEPOINT active_record_1
   (0.1ms)  SELECT 1 FROM "users" WHERE LOWER("users"."name") = LOWER('testowy') LIMIT 1
   (0.1ms)  SELECT 1 FROM "users" WHERE LOWER("users"."email") = LOWER('[email protected]') LIMIT 1
   (0.1ms)  SELECT COUNT(*) FROM "users" 
   (0.1ms)  SAVEPOINT active_record_1
   (0.1ms)  SELECT 1 FROM "users" WHERE LOWER("users"."name") = LOWER('johnkowalski') LIMIT 1
   (0.1ms)  SELECT 1 FROM "users" WHERE LOWER("users"."email") = LOWER('[email protected]') LIMIT 1
  SQL (0.3ms)  INSERT INTO "users" ("created_at", "email", "fullname", "name", "password_digest", "updated_at") VALUES (?, ?, ?, ?, ?, ?)  [["created_at", Thu, 28 Jul 2011 13:09:48 UTC +00:00], ["email", "[email protected]"], ["fullname", "John Kowalski"], ["name", "johnkowalski"], ["password_digest", "$2a$10$e1.3fifGcs7PALH1o0GQJ.Ny/QxCS9fRxDJ6NemGkwfFJCpsD51vy"], ["updated_at", Thu, 28 Jul 2011 13:09:48 UTC +00:00]]
   (0.0ms)  RELEASE SAVEPOINT active_record_1

But when I run these tests and integration tests I found this in log:

 (0.1ms)  SELECT 1 FROM "users" WHERE LOWER("users"."email") = LOWER('[email protected]') LIMIT 1
   (0.1ms)  SELECT COUNT(*) FROM "users" 
   (0.1ms)  SAVEPOINT active_record_1
   (0.1ms)  SELECT 1 FROM "users" WHERE LOWER("users"."name") = LOWER('johnkowalski') LIMIT 1
  CACHE (0.0ms)  SELECT 1 FROM "users" WHERE LOWER("users"."email") = LOWER('[email protected]') LIMIT 1
   (0.0ms)  ROLLBACK TO SAVEPOINT active_record_1

So the user object is bringing from memory not from database. But by default caching is disabled in test environment, and I have config.action_controller.perform_caching = false in environment/test.rb

How to disable caching in that case?

2
  • This is really weird, what validations do you hve in the model? Commented Jul 28, 2011 at 9:56
  • 1
    GAH, don't use test1 and test2 as your test names! The whole point of the rSpec DSL is that you should describe your tests in something resembling natural language. Instead of it "test1" you should always use it "should be invalid" or some other actually descriptive name. Commented Jul 28, 2011 at 13:09

1 Answer 1

2

Always hard to guess how much you know about rspec, so maybe this is all very known to you.

But: are you sure you have the following line in your spec_helper.rb:

config.use_transactional_fixtures = true

This will make sure that after each test all created models are released. Note that everything in a before(:each) takes place inside the transaction (and is rolled back) and what is inside a before(:all) does not (and needs to be cleaned in a after(all).

So, if you use transactional fixtures, it is actually normal that User.count == 0, since all creates are rolled back.

Also there is no use in creating a user four times, on the same level, as the result would (normally be the same).

Secondly, since you seem to be testing your validations, I would suggest taking a look at shoulda, which offers nice shortcuts. E.g. something like

it { should validate_uniqueness_of :email }
Sign up to request clarification or add additional context in comments.

4 Comments

Yes, I have this in my spec_helper.rb. And I know that User.count == 0 should be valid. All I don't understand is why only test5 fail? I know that testing 3 times the same thing is not necessary. These are only tests for understanding this strange behavior of rspec.
@chg: ok, good you know these things, because that was hard to deduce from your question or profile. So, somehow the integration spec messes with the result of the model test. Weird. And why only test4 or test5? It should fail on the first try if that was the cause. What happens if the integration-test passes? Do you run your tests in parallel? Try to find the simplest case that fails.
I'm having some strange issues with savepoints being released at odd times too. So I'll create a fixture, but then the log shows the savepoint is released immdiately before the request being made on the next line...
If you are on MySQL, it doesn't support nested transactions. You will need to do manual cleanup of your database after these tests run. See this answer: stackoverflow.com/questions/13161394/…

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.