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: "kowalski@example.com", 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: "kowalski@example.com", 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: "kowalski@example.com", 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: "kowalski@example.com", 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('kowalski@example.com') 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('kowalski@example.com') 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", "kowalski@example.com"], ["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('kowalski@example.com') 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('kowalski@example.com') 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?

chg
  • 93
  • 2
  • 6
  • This is really weird, what validations do you hve in the model? – apneadiving Jul 28 '11 at 09: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. – user229044 Jul 28 '11 at 13:09

1 Answers1

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 }
nathanvda
  • 49,707
  • 13
  • 117
  • 139
  • 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 Jul 28 '11 at 12:06
  • @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. – nathanvda Jul 28 '11 at 12:35
  • 2
    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... – Kevin Jun 14 '13 at 03:14
  • 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: http://stackoverflow.com/questions/13161394/activerecord-error-savepoint-active-record-1-does-not-exist#answer-13732210 – Nigel Sheridan-Smith Jun 27 '16 at 04:58