2

For my Rails 3 application I use FactoryGirl together with shoulda context (1.0.0.beta1) and matchers (1.0.0.beta3) for my functional tests. My problem: in the code example below, the assign_to test fails because @user - to my surprise - turns out to be nil. In the outer setup block, @user is assigned a valid model instance, but from within the should assign_to statement the instance variable is not accessible. Why is that and what is the correct way to write that test?

class UsersControllerTest < ActionController::TestCase
  context "as admin" do
    setup do
      @user = Factory.create(:user)
    end

    context "getting index" do
      setup do
        get :index
      end

      should assign_to(:users).with([@user])
    end
end
dblp1
  • 35
  • 2
  • I'm experiencing the same issue, seems like a bug. It only happened after updating from 3.0.0.beta2 to the latest git HEAD. However, I need the latest version from master since a release has not been made in over a year. – Matt Huggins Feb 20 '12 at 01:50

3 Answers3

0

I discovered that passing the value as a black miraculously works. However, after digging into the actual AssignToMatcher code, it doesn't seem to make sense why the parameter method wouldn't work while the block method would.

For the sake of the +250 rep I'm investing in this, I'd still like an answer that explains why the param method isn't working (and how to fix it), but until then, at least I have a workaround.

@dblp1, hopefully this works for you too. Here's an example specific to your code:

class UsersControllerTest < ActionController::TestCase
  context "as admin" do
    setup do
      @user = Factory.create(:user)
    end

    context "getting index" do
      setup do
        get :index
      end

      should assign_to(:users).with { [@user] }
    end
  end
end
Matt Huggins
  • 81,398
  • 36
  • 149
  • 218
  • 1
    I would first remove the square brackets `should assign_to(:users).with(@user)`. It seems that there was a bug on this exact topic (https://github.com/thoughtbot/shoulda/issues/157). If it still does not work, it is a bug and you should reopen the issue. Why with the block works it is clear to me. :-) I hope it helps. –  Feb 25 '12 at 04:17
  • Good catch on the issue. That seems to explain why this method is working over the param method, though I don't know why it's still an issue for me considering I've got the latest release. – Matt Huggins Feb 25 '12 at 16:19
  • it *shoulda* have been fixed :-). What is the exact error message? –  Feb 26 '12 at 02:08
  • When I use the param method instead of the block method, I get an error along the lines of: `Expected action to assign nil for user, but got #` – Matt Huggins Feb 27 '12 at 19:25
  • It's worth noting that I'm using the latest release of shoulda (3.0.0), which was released a couple days ago. I was using the latest github version prior to that, so I shouldn't still be running into issue #157 that you linked to, unless the error somehow made it back into the code. I don't see how that's possible though since the [AssignToMatcher hasn't been updated in 11 months](https://github.com/thoughtbot/shoulda-matchers/commit/de11b4b59bed0cdd79e4b958f378d9d3c3c1d598). – Matt Huggins Feb 27 '12 at 19:27
0

(I am pasting as an answer as it is fairly long)

I mocked a bit your test and checked what was the results passed.

Interestingly enough, when I call the matcher @post=null (as I believe in your case). The issues I think (after a while of investigation), it coming from the the order the setup do block is called and the fact that the variables defined in one context are not visible in the nested context.

modified class

context "as admin" do
 setup do
   @post = Post.new
   puts "Step 1 - inside setup do"
   puts @post.class
 end
 puts "Step 2 - outside setup do 1"
 puts @post.class

 context "getting index" do
   setup do
     get :index
   end
   puts "Step 3 - calling shoulda"
   puts @post.class
   should assign_to(:posts).with([@post])
   #should assign_to(:posts).with  { [@post] }
   end
end

And the results in the console

ruby -I test test/functional/posts_controller_test.rb
Step 2 - outside setup do 1
NilClass
Step 3 - calling shoulda
NilClass
Loaded suite test/functional/posts_controller_test
Started
Step 1 - inside setup do
Post

So the setup cycle is called at the end (and not at the beginning) and then your is Nil when passed to the matcher.

Even if I remove the first setup do does not work pretty well.

Step 1 - inside setup do
Post
Step 2 - outside setup do 1
Post
Step 3 - calling shoulda
NilClass

Finally, putting the post in the inner context

Step 3 - calling shoulda
Post

If you call @user = Factory.create(:user) directly inside the "getting index" context, I believe it will work.

  • I know that makes it look like it's working out of order, but it's not. It requires a bit of deeper understanding about how [blocks](http://www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/) work. Just because any given block is defined prior to one of your `puts` calls does not mean that it will be called/evaluated prior to the aforementioned `puts` calls. It simply means that the block was defined first, but its calling order could come at any point. Thanks though! – Matt Huggins Feb 27 '12 at 19:20
-1

When you are working with indexes you should use the plural of the instance variable.

@users rather than @user

You should also populate it as an array.

Finally, Shoulda matchers should start with "it" and be contained in braces, at least in the RSpec world, which is what I use. Not sure if this is the case with Test::Unit or whether your formatting above will work.

Try something like this.

class UsersControllerTest < ActionController::TestCase
  context "as admin" do
    setup do
      @user = Factory.create(:user)
    end

    context "getting index" do
      setup do
        @users = Array.new(3) { Factory(:user) }
        get :index
      end

      it { should assign_to(:users).with(@users) }
    end
end
nmott
  • 9,454
  • 3
  • 45
  • 34
  • Your suggestions are syntactical preferences and will not change the outcome. Unfortunately, it is an actual issue and has nothing to do with the naming of the variables. Also, the `it` method is part of Rspec, not Test::Unit. – Matt Huggins Feb 20 '12 at 01:54