Problem z RSpec

Mam takie testy:

spec/models/user_spec.rb

[code=ruby]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 "should reject duplicate email addresses" do
  User.create!(@attr)
  user_with_duplicate_email = User.new(@attr.merge(name: "nowak"))
  user_with_duplicate_email.should_not be_valid
end

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

end
end[/code]
/spec/requests/users_spec.rb

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
Wszystkie przechodzą, jednak chciałbym zrozumieć dlaczego gdy usunę metodę create z controllera Users mam następujące błedy:

[code] 1) Users signup failure should not make a new user
Failure/Error: click_button
AbstractController::ActionNotFound:
The action ‘create’ could not be found for UsersController
# ./spec/requests/users_spec.rb:13:in block (5 levels) in <top (required)>' # ./spec/requests/users_spec.rb:7:inblock (4 levels) in <top (required)>’

  1. User Create a user test3
    Failure/Error: User.create!(@attr)
    ActiveRecord::RecordInvalid:
    Validation failed: Email has already been taken

    ./spec/models/user_spec.rb:26:in `block (3 levels) in <top (required)>’[/code]

Pierwszy jest oczywisty, ale dlaczego nagle drugi się pojawia?
Przy czym, jeżeli wykonam tylko testy dla modelu to nie ma problemu:

[code]rspec spec/models

Finished in 0.53837 seconds
4 examples, 0 failures[/code]
Byłby ktoś tak miły i wytłumaczył mi dlaczego tak się dzieje?

A jak czyścisz bazę danych?

Ja stawiam, że w testach controllerów nie czyści Ci bazy danych, a później lecą testy modeli i próbujesz stworzyć usera, gdy już taki istnieje. Dlatego też, gdy puszczasz same modele, to testy przechodzą.

Wszystkie testy, które wykonuje umieściłem powyżej. Problem z Twoim wytłumaczeniem jest taki, że test controllera nie wprowadza nic do bazy danych. Na dobrą sprawę, to gdy nie ma metody create to nic nie wykonuje. Dodatkowo, rozpatrując tylko testy modeli, dlaczego wysypuje się na test3, skoro specjalnie wcześniej umieściłem 3 testy, które tworzą tego samego użytkownika i tam wszystko gra.

Dodatkowo jeśli zamienię kolejnością test3 z “should reject…” to też wszystko gra:

[code=ruby]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)
end
it "should reject duplicate email addresses" do
  User.create!(@attr)
  user_with_duplicate_email = User.new(@attr.merge(name: "nowak"))
  user_with_duplicate_email.should_not be_valid
end

end
end[/code]

Może spróbuj podłączyć debuggera przed każdym testem i sprawdzaj stan bazy, aż wpadniesz na rozwiązanie. Możesz też wrzucić aplikację na githuba jeśli nie jest objęta tajemnicą. A jak jest, gdy dodasz do test1-3 jakieś matchery?

Projekt na githubie

Dla takich testów nie ma problemów:

[code=ruby]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
  t = User.create!(@attr)
  t.email.should == "kowalski@example.com"
end

it "test2" do
  test = User.create!(@attr)
  test.name.should == "johnkowalski"
end

it "should reject duplicate email addresses" 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 "test3" do
  User.create!(@attr)
end

end
end[/code]
Gdy odkomentuję linię a.should_not be_valid, test3 zgłasza błąd, ale tylko gdy wykonuję testy z tego pliku oraz z testu controllera. Same testy modelu są zawsze poprawne, niezależnie od tej linii. Dopiero poznaję Railsy i szczerze powiedziawszy nie mam pojęcia jaka zależność powoduje taki problem.

Dobra, już wiem skąd ten problem. Otóż uruchamiając rspec tylko dla modelu w logach mam:

(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 (3.4ms) INSERT INTO "users" ("created_at", "email", "fullname", "name", "password_digest", "updated_at") VALUES (?, ?, ?, ?, ?, ?) [["created_at", Thu, 28 Jul 2011 13:26:38 UTC +00:00], ["email", "kowalski@example.com"], ["fullname", "John Kowalski"], ["name", "johnkowalski"], ["password_digest", "$2a$10$ZjQ4VIEHazv3Pg7tvl8DludmxPq4ZwVV7Iafb8ewlDi1DPUIDVhci"], ["updated_at", Thu, 28 Jul 2011 13:26:38 UTC +00:00]] (0.1ms) RELEASE SAVEPOINT active_record_1 (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:26:38 UTC +00:00], ["email", "kowalski@example.com"], ["fullname", "John Kowalski"], ["name", "johnkowalski"], ["password_digest", "$2a$10$q2nkzJ.yJJQkPiZgHJ6uhO9fsZHYXGx56nmHr6ZKes2STBO/netiu"], ["updated_at", Thu, 28 Jul 2011 13:26:38 UTC +00:00]] (0.0ms) RELEASE SAVEPOINT active_record_1 (0.1ms) SAVEPOINT active_record_1 (0.1ms) SELECT 1 FROM "users" WHERE LOWER("users"."name") = LOWER('johnkowalski') LIMIT 1 (0.0ms) 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:26:38 UTC +00:00], ["email", "kowalski@example.com"], ["fullname", "John Kowalski"], ["name", "johnkowalski"], ["password_digest", "$2a$10$lZA8z3pCtclp71AOhw.88.cISKqeBKRO/Vg9V7mOKQwuUTsY1wXsC"], ["updated_at", Thu, 28 Jul 2011 13:26:38 UTC +00:00]] (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:26:39 UTC +00:00], ["email", "kowalski@example.com"], ["fullname", "John Kowalski"], ["name", "johnkowalski"], ["password_digest", "$2a$10$zVYlkQkPrDhwOJCLJ7HkSu2eBbI0c9QIFoxC7Tt5xHMLuAjM9ARZq"], ["updated_at", Thu, 28 Jul 2011 13:26:39 UTC +00:00]] (0.0ms) RELEASE SAVEPOINT active_record_1
Podczas gdy uruchamiając wszystkie testy, tzn. model + ten 1 integration test to wtedy w logu mam:

Started GET "/signup" for 127.0.0.1 at 2011-07-28 15:27:49 +0200 (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 (3.4ms) INSERT INTO "users" ("created_at", "email", "fullname", "name", "password_digest", "updated_at") VALUES (?, ?, ?, ?, ?, ?) [["created_at", Thu, 28 Jul 2011 13:27:49 UTC +00:00], ["email", "kowalski@example.com"], ["fullname", "John Kowalski"], ["name", "johnkowalski"], ["password_digest", "$2a$10$DtzBctUHfPFqYhtPA268l.aKgLw2pVEzC.fr74IQrV3lfJeNqPQqC"], ["updated_at", Thu, 28 Jul 2011 13:27:49 UTC +00:00]] (0.1ms) RELEASE SAVEPOINT active_record_1 (0.0ms) SAVEPOINT active_record_1 (0.1ms) SELECT 1 FROM "users" WHERE LOWER("users"."name") = LOWER('johnkowalski') LIMIT 1 (0.0ms) 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:27:49 UTC +00:00], ["email", "kowalski@example.com"], ["fullname", "John Kowalski"], ["name", "johnkowalski"], ["password_digest", "$2a$10$DxJ64G70jafdCiTpvMP6He8FPhpupgyL9G.ANW3Gx2cCsohueMSva"], ["updated_at", Thu, 28 Jul 2011 13:27:49 UTC +00:00]] (0.0ms) RELEASE SAVEPOINT active_record_1 (0.0ms) 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:27:49 UTC +00:00], ["email", "kowalski@example.com"], ["fullname", "John Kowalski"], ["name", "johnkowalski"], ["password_digest", "$2a$10$kEEOByyUOMIpUr2XHqxeouy49XFXs.hIS0LcsYEW.GKZhVaUztPA."], ["updated_at", Thu, 28 Jul 2011 13:27:49 UTC +00:00]] (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.0ms) 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
Czyli niewiadomo dlaczego, wartość dla ostatniego testu jest brana nie z bazy danych ale z cache. Dalej nie rozumiem dlaczego akurat ta wartość jest brana z cache i dlaczego tylko jeśli wysypie się jakiś integration test ale może teraz uda się rozwiązać ten problem.
O ile wiem cache jest domyślnie wyłączony dla “test”. To jak wymusić aby nic z cache nie brał?

EDIT:
Ustawienie config.cache_classes na false rozwiązuje problem. Jednak jeśli ktoś umiał mi wytłumaczyć dlaczego wcześniej brał te dane z cache to chętnie się dowiem.

Myślę, że to może być bug w Rails i twój test używa ActiveRecord::QueryCache middleware. Mógłbyś zrobić downgrade do rc4 i zobaczyć kilkukrotnie czy uda Ci się powtórzyć błąd ?

Próbowałem na różne sposoby i na rc4 wszystko działa bez problemu (jeśli chodzi o testy, bo na rc4 mam jakieś problemy z routingiem do assetów, ale z tego co widzę, to akurat znany bug)

No to znalazłeś buga w rc5 tak myślę. Postaraj się go dobrze wyizolować i najprostszych słować opisać issue na github i liczmy na to, że zostanie rozwiązany. Powinien bo jest dość poważny.

Zrobione. Dzięki za pomoc.

Jak tam sytuacja się rozwija? Możesz dać link do buga ?

Mega dużo się dzieje w tym bugu. Wygląda na to, że nawet najlepsi mieli przy tym niezłą zagwozdkę. Polecam lekturę.