Marcin Doliwa
Marcin Doliwa

Reputation: 3659

Console says variable is not nil, in tests it's nil

Have simple test:

  context "in the same board" do
    @link = FactoryGirl.create(:link_with_board, url: "www.onet.pl")
    @board = @link.board
    it "is invalid when the same url already exists" do
      expect(@board.links.build(url: "www.onet.pl")).to_not be_valid
      expect(@board.links.build(url: "http://www.onet.pl")).to_not be_valid
      expect(@board.links.build(url: "www.onet.pl/")).to_not be_valid
    end
  end

It shows me error:

Failures:

  1) Link in the same board is invalid when the same url already exists
     Failure/Error: expect(@board.links.build(url: "www.onet.pl")).to_not be_valid
     NoMethodError:
       undefined method `links' for nil:NilClass
     # ./spec/models/link_spec.rb:50:in `block (3 levels) in <top (required)>'

When I try the same in console, it all works fine. Any idea why?

Update:

Ok, I made it work, but still question remains the same, why the first one didn't work?

  context "in the same board" do
    #FIX START
    before :each do
      @link = FactoryGirl.create(:link_with_board, url: "www.onet.pl")
      @board = @link.board
    end
    #FIX END
    it "is invalid when the same url already exists" do
      expect(@board.links.build(url: "www.onet.pl")).to_not be_valid
      expect(@board.links.build(url: "http://www.onet.pl")).to_not be_valid
      expect(@board.links.build(url: "www.onet.pl/")).to_not be_valid
    end
  end

Upvotes: 0

Views: 94

Answers (2)

georgebrock
georgebrock

Reputation: 30023

You are setting up @link and @board in a context block. This is a different scope to the it block, so when the it block runs those variables aren't defined.

Behind the scenes, context creates a class and it creates a method, so what you were doing was something similar to this:

class MyExampleGroup
  @link = FactoryGirl.create(:link_with_board, url: "www.onet.pl")
  @board = @link.board

  def it_is_invalid_sometimes
    @board.nil? # => true
  end
end

An instance variable set in class scope is visible in the class, but not in instances of the class.

When you moved them to before, the generated structure was more like this:

class MyExampleGroup
  def before
    @link = FactoryGirl.create(:link_with_board, url: "www.onet.pl")
    @board = @link.board
  end

  def it_is_invalid_sometimes
    @board.nil? # => false
  end
end

Now the instance variables are being defined in instance scope, so they work as you expect.

(I'm simplifying slightly for clarity: The methods created by it calls actually return RSpec::Core::Example objects, and the before blocks are run on those objects using instance_exec.)

Upvotes: 3

Domon
Domon

Reputation: 6823

If you start the console with a simple rails console command, it connects to the development database (in a normal setup). But the tests are run against the test database.

In your test, the line creating the @link:

@link = FactoryGirl.create(:link_with_board, url: "www.onet.pl")

You might want to check how the :link_with_board factory is defined. (Probably in spec/factories/*.rb.)

Upvotes: 1

Related Questions