3

The instruction is this:

Following the template in Listing 11.68, write a test of the image uploader in Section 11.4. As preparation, you should add an image to the fixtures directory (using, e.g, cp app/assets/images/rails.png test/fixtures/). (If you’re using Git, I also recommend updating your .gitignore file as shown in Listing 11.69.) To avoid a confusing error, you will also need to configure CarrierWave to skip image resizing in tests by creating an initializer file as shown in Listing 11.70. The additional assertions in Listing 11.68 check both for a file upload field on the Home page and for a valid image attribute on the micropost resulting from valid submission. Note the use of the special fixture_file_upload method for uploading files as fixtures inside tests.22 Hint: To check for a valid picture attribute, use the assigns method mentioned in Section 10.1.4 to access the micropost in the create action after valid submission.

Here's my code in test/integration/micrposts_interface_test.rb

  test "micropost interface" do
    log_in_as(@user)
    get root_path
    assert_select 'div.pagination'
    assert_select 'input[type=FILL_IN]'
    # Invalid submission
    assert_no_difference 'Micropost.count' do
      post microposts_path, micropost: { content: "" }
    end
    assert_select 'div#error_explanation'
    # Valid submission
    content = "This micropost really ties the room together"
    picture = fixture_file_upload('test/fixtures/rails.png', 'image/png')
    assert_difference 'Micropost.count', 1 do
      post microposts_path, micropost: { content: content, picture: FILL_IN }
    end
    assert FILL_IN.picture?
    follow_redirect!
    assert_match content, response.body
    # Delete a post.
    assert_select 'a', text: 'delete'
    first_micropost = @user.microposts.paginate(page: 1).first
    assert_difference 'Micropost.count', -1 do
      delete micropost_path(first_micropost)
    end
    # Visit a different user.
    get user_path(users(:archer))
    assert_select 'a', text: 'delete', count: 0
  end

In assert_select 'input[type=FILL_IN]', my fill-in is type=file

In post microposts_path, micropost: { content: content, picture: FILL_IN }, my fill-in is picture: true

In assert FILL_IN.picture? is @user.microposts.picture?

The error I get is: NoMethodError: NoMethodError: undefined method `picture?' for #

So I think my first two FILL_INs are correct. The problem comes from the third fill-in. I wrote @user.microposts because I thought to test for the presence of a picture?, it'll have to be on the microposts of the user. But the error was no method for picture?

I tried also @user.micropost.picture?, and the error was NoMethodError: NoMethodError: undefined method `micropost' for #

I thought my line of reasoning was correct, but apparently not. Help! I'm a complete newb.

Char
  • 105
  • 11

4 Answers4

4

My solution, it seems to work:

test "micropost interface" do
  log_in_as(@user)
  get root_path
  assert_select 'div.pagination'
  assert_select 'input[type=file]'
  # Invalid submission
  assert_no_difference 'Micropost.count' do
    post microposts_path, micropost: { content: "" }
  end
  assert_select 'div#error_explanation'
  # Valid submission
  content = "This micropost really ties the room together"
  picture = fixture_file_upload('test/fixtures/sample-image.jpg', 'image/jpg')
  assert_difference 'Micropost.count', 1 do
    post microposts_path, micropost: { content: content, picture: picture }
  end
  assert assigns(:micropost).picture?
  assert_redirected_to root_url
  follow_redirect!
  assert_match content, response.body
  # Delete a post.
  assert_select 'a', text: 'delete'
  first_micropost = @user.microposts.paginate(page: 1).first
  assert_difference 'Micropost.count', -1 do
    delete micropost_path(first_micropost)
  end
  # Visit a different user.
  get user_path(users(:archer))
  assert_select 'a', text: 'delete', count: 0
end
eCorners
  • 67
  • 8
3

Little late in answering this, but maybe this will help someone else down the road. I gave long and short answers for each, if you want the quick explanation, read the TLDR at the end of the question, if you want a little more explanation, feel free to read in full :)

1) assert_select 'input[type=FILL_IN]'

Using either file or "file" will pass the test. According to the documentation on assert_select here: link, "The selector may be a CSS selector expression (String), an expression with substitution values, or an HTML::Selector object." Essentially, it simply needs to be a valid CSS selector, which, according to this stackoverflow post: link, depending on the situation (and it's generally not needed if it's an alphanumeric string, as is the case here), it may or may not need quotes, but it is generally good practice to use them, so that there is no potential for confusion/errors.

TLDR: using either 'input[type="file"]' or 'input[type=file]' will work in this case, but using quotes is generally considered a good practice to avoid errors.

2) post microposts_path, micropost: { content: content, picture: FILL_IN }

For this one, as @Nick mentioned, you probably want to just use the variable 'picture' that was created on the line above the line with assert_difference. Remember that when you added the picture attribute to the micropost model, you ran the command:

rails generate migration add_picture_to_microposts picture:string

which would mean that it's expecting you to pass a string, not a boolean, when creating a micropost. Also note what the test is actually doing:

assert_difference 'Micropost.count', 1 do
    post microposts_path, micropost: { content: content, picture: FILL_IN }
end

The code is actually creating a micropost by posting to the microposts path, and then checking to make sure the count increased by 1.

Now, you might be saying: but the picture variable isn't a string! This is true, and I'm not an expert on CarrierWave and I haven't really dug into its code, but it seems like it's actually expecting a file path and a MIME (file extension) type, based on the documentation for fixture_file_upload, found here: link. If you remember from the tutorial, you added the line:

mount_uploader :picture, PictureUploader

to your microposts model, which appears to be associating the picture attribute with the PictureUploader class (found in app/uploaders/picture_uploader.rb), which has the following in the class file:

class PictureUploader < CarrierWave::Uploader::Base

which means it's inheriting from the CarrierWave::Uploader::Base class, which probably means that CarrierWave is messing with the picture attribute to take something other than a string (which as I mentioned, it appears it takes a path and a MIME type based on the documentation), but a boolean won't work. (For the record, I did some testing, passing in a random string and then a random file path, and both failed as expected -- and so did passing a boolean).

Also, and admittedly it's a small detail, but even if passing in a boolean worked, it would only generally test for it being a picture, as opposed to a particular picture, which would be another argument for using the picture variable.

TLDR: you should probably just use the picture variable: post microposts_path, micropost: { content: content, picture: picture }

3) assert FILL_IN.picture?

You mentioned you used the code:

@user.microposts.picture?

And got a NoMethodError. The reason for this is that @user.microposts will return a collection of microposts, not an individual micropost, and so it is confused (specifically, it returns an ActiveRecord_Associations_CollectionProxy, which does not have a picture? method, hence, you get a NoMethodError. Also, don't forget, you can open up the rails console, and run User.first.microposts.class to find out what kind of object is being returned by @user.microposts). Remember that the picture? method only works on an individual micropost, so you'll want to do something like:

@user.microposts.first.picture?

which will check the most recently created micropost (microposts are sorted by the date/time they were created, with the most recent being first) for a picture.

However, there is another way to go about doing it, which uses the hint Hartl gives in the exercise: "To check for a valid picture attribute, use the assigns method mentioned in Section 10.1.4 to access the micropost in the create action after valid submission"

If you remember, you can access instance variables from the controller by using the assigns method. The create action in the microposts controller has an instance variable @micropost, which will be the most recent micropost created by the user as well.

The code would look like:

foo = assigns(:micropost)
assert foo.picture?

In fact, you can prove to yourself that both methods would work, by adding the following code:

foo = assigns(:micropost)

assert foo.picture?
assert @user.microposts.first.picture?

assert_equal foo, @user.microposts.first

So what this code does is the following: first, it creates a variable foo and assigns the newly created micropost to it. It then checks that foo.picture? is true, and then checks the other method, @user.microposts.first.picture? and checks that it's also true. Lastly, it checks that foo and the most recent micropost for @user are the same thing, and all of these tests should pass.

TLDR: you were calling the picture? method on a collection of microposts, not an individual micropost. You can use either @user.microposts.first.picture? or, if you want to use the assigns() method, you can use the that as well, with the following:

foo = assigns(:micropost)
assert foo.picture?

Hope that helps!

Community
  • 1
  • 1
2

I belief the first FILL_IN should read type="file" to check that such an input is available.

For the second FILL_IN I think the solution is { content: content, picture: picture } where the last 'picture' refers to two lines above that (where you define this variable).

The thirs and last FILL_IN I'm not sure about. Given the hint that Hartl offers, I think it should be:

plaatje = assigns(:micropost)
assert plaatje.picture?

But I'm also a newbie and following the tutorial, so I might be wrong. Let me know if it works.

With these solutions the full code is:

test "micropost interface" do
  log_in_as(@user)
  get root_path
  assert_select 'div.pagination'
  assert_select 'input[type="file"]'
  # Invalid submission
  assert_no_difference 'Micropost.count' do
    post microposts_path, micropost: { content: "" }
  end
  assert_select 'div#error_explanation'
  # Valid submission
  content = "This micropost really ties the room together"
  pict = fixture_file_upload('test/fixtures/rails.png', 'image/png')
  assert_difference 'Micropost.count', 1 do
    post microposts_path, micropost: { content: content, picture: pict }
  end
  plaatje = assigns(:micropost)
  assert plaatje.picture?
  assert_redirected_to root_url
  follow_redirect!
  assert_match content, response.body
  # Delete a post.
  assert_select 'a', text: 'delete'
  first_micropost = @user.microposts.paginate(page: 1).first
  assert_difference 'Micropost.count', -1 do
    delete micropost_path(first_micropost)
  end
  # Visit a different user.
  get user_path(users(:archer))
  assert_select 'a', text: 'delete', count: 0
end
Nick
  • 3,496
  • 7
  • 42
  • 96
0

The third FILL_IN can actually be filled in using: @user.microposts.first.picture? Your first solution doesn't work because you have to specify which micropost you want to query.

cristen
  • 1
  • 2