Specing a manual call to valid? - ruby-on-rails

Hey all, I am completely lost on this one.
I found a code snippet online to help validate fields via ajax as the user types into them. So I'm trying to write a spec against part of it and I just can't get it to pass.
Here's the code
def validate
field = params[:field]
user = User.new(field => params[:value])
output = ""
user.valid?
if user.errors[field] != nil
if user.errors[field].class == String
output = "#{field.titleize} #{user.errors[field]}"
else
output = "#{field.titleize} #{user.errors[field].to_sentence}"
end
end
render :text => output
end
and here is my test so far
describe "POST validate" do
it "retrieves the user based on the past in username" do
mock_errors ||= mock("errors")
mock_errors.stub!(:[]).and_return(nil)
User.should_receive(:new).with({'username'=>"UserName"}).and_return(mock_user)
mock_user.should_receive(:valid?).and_return(true)
mock_errors.should_receive(:[]).with("username").and_return(nil)
put :validate, :field=>'username', :value=>'UserName'
response.should == ""
end
end
I get this error -
1) Spec::Mocks::MockExpectationError
in 'UsersController POST validate
retrieves the user based on the past
in username' Mock 'errors' received
unexpected message :[] with
("username")
I can't seem to figure out how in the world to mock the call to user.errors[field]. Ideally this spec tests the happy path, no errors. I'll then write another for a validation failure.

I'm not seeing mock_user. Here's a shot at it:
describe "POST validate" do
it "retrieves the user based on the past in username" do
mock_errors = mock("errors")
mock_user = mock("user")
mock_user.stub!(:errors).and_return([mock_errors])
mock_errors.stub!(:[]).and_return(nil)
User.should_receive(:new).with({'username'=>"UserName"}).and_return(mock_user)
mock_user.should_receive(:valid?).and_return(true)
mock_errors.should_receive(:[]).with("username").and_return(ActiveRecord::Errors.new({}))
put :validate, :field=>'username', :value=>'UserName'
response.should == ""
end
end
The key is that you need your User mock to respond to the errors method by returning either an empty hash or a hash of fieldname/errors. An alternative to this is to use one of the fixture replacement tools. I'm using machinist right now, which might reduce this whole thing to:
describe "POST validate" do
it "retrieves the user based on the past in username" do
#user = User.make{'username'=>"UserName"}
#user.should_receive(:valid?).and_return(true)
#user.errors.should_receive(:[]).with("username").and_return(ActiveRecord::Errors.new({}))
put :validate, :field=>'username', :value=>'UserName'
response.should == ""
end
end

Related

rspec #variable returning nil

I have an issue with my #attributes variable. I would like it to be accessible to keep my code dry, but currently, I have to restate the variable and set it to "values" to get my rspec test to work. What is a better way to do this without duplicating the values.
ref: Unexpected nil variable in RSpec
Shows that it is not accessible in describe, but there needs be another solution. When would "specify" be appropriate? I have not used it.
describe "When one field is missing invalid " do
before(:each) do
#user = create(:user)
#attributes = {"has_car"=>"true", "has_truck"=>"true", "has_boat"=>"true", "color"=>"blue value", "size"=>"large value"}
end
values = {"has_car"=>"true", "has_truck"=>"true", "has_boat"=>"true", "color"=>"blue value", "size"=>"large value"}
values.keys.each do |f|
p = values.except(f)
it "returns invalid when #{f.to_s} is missing" do
cr = CarRegistration::Vehicle.new(#user, p)
cr.valid?
end
end
end
Update based on comments:
I would also like to use the values array hash in other tests. If I put it in the loop as stated, I would still have to repeat it in other places. Any other recommendations?
Update: I tried using let(),
describe "When one field is missing" do
let(:user) {Factorybot.create(:user)}
let(:attributes) = {{"has_car"=>"true", "has_truck"=>"true", "has_boat"=>"true", "color"=>"blue value", "size"=>"large value"}}
attributes do |f|
p = attributes.except(f)
it "returns invalid when #{f.to_s} is missing" do
cr = CarRegistration::Vehicle.new(user, p)
cr.valid?
end
end
end
but get the following error.
attributes is not available on an example group (e.g. a describe or context block). It is only available from within individual examples (e.g. it blocks) or from constructs that run in the scope of an example (e.g. before, let, etc).
In either of your snippets, you don't need attributes inside of your specs. It is data to generate specs. As such, it must live one level above.
describe "When one field is missing" do
let(:user) { Factorybot.create(:user) }
attributes = { "has_car" => "true", "has_truck" => "true", "has_boat" => "true", "color" => "blue value", "size" => "large value" }
attributes do |f|
p = attributes.except(f)
it "returns invalid when #{f.to_s} is missing" do
cr = CarRegistration::Vehicle.new(user, p)
cr.valid?
end
end
end
As you seem to have recognized, based on the other SO post you linked to, you can't refer to your instance variables out in your describe block. Just set it as a local variable as you've done.
Using let
describe "When one field is missing" do
let(:user) {Factorybot.create(:user)}
let(:attributes) = {{"has_car"=>"true", "has_truck"=>"true", "has_boat"=>"true", "color"=>"blue value", "size"=>"large value"}}
## The variables are used INSIDE the it block.
it "returns invalid when a key is missing" do
attributes do |f|
p = attributes.except(f)
cr = CarRegistration::Vehicle.new(user, p)
expect(cr.valid?).to eq(true) # are you testing the expectation? Added this line.
end
end
end
Personally I don't like writing test (like the above) which could fail for multiple reasons. Sergio is correct. But if you want to use let you have to make use of it from WITHIN the it block - this example shows that.

the right way to change the associated object in rspec

I recently started to test with rspec, so I can strongly be mistaken, correct me if there is a better way
I create two related models
let(:user) {FactoryGirl.create :user}
let!(:participation) {FactoryGirl.create :participation, user: user}
and before one of the tests change one of the related objects
context "when" do
before {participation.prize = 100}
it "" do
binding.pry
end
end
But inside it
participation.prize => 100
user.participatons.select(:prize) => nil
what am I doing wrong ? and how to fix it?
When you say user.participations.select(:prize), you're making a query to the db to get values in the user's participations' prize columns. But when you say before {participation.prize = 100} you're only setting the prize attribute on the participation object. Try saving the participation before the select line:
participation.prize # => 100
participation.save
user.participatons.select(:prize) # => nil
Another possible issue is that user.participations has been memoized by a previous call. Ensure that user.participations.first == participation. If it doesn't, check
1) puts participation.user_id and
2) puts user.participations, user.reload.participations
Lastly, a better way of setting up the test so that you run into this issue less often is something along the lines of:
# let(:price) { 0 } # default price. Optional so that tests won't throw errors if you forget to set it in a context/describe block.
let(:user) {FactoryGirl.create :user}
let!(:participation) {FactoryGirl.create :participation, user: user, price: price}
# ...
context "when ..." do
let(:price) { 100 }
it "" do
binding.pry
end
end
This way, the price is set when you create the model. Following this pattern generally means running into this problem less.

How to delete an entire array in Ruby and test with RSpec

I'm fairly new to Ruby and am currently taking a full stack course. For one of my projects we are building an addressbook. I have set up how to add an entry to the addressbook, however, I can't seem to figure out how to delete an entry (I make an attempt with the remove_entry method in the AddressBook class below but am not having any luck). We are also supposed to test first with RSpec, have the test fail and then write some code to get it to pass. If I didn't include all the info needed for this question let me know (rookie here). Anyway, here is what I have so far:
RSpec
context ".remove_entry" do
it "removes only one entry from the address book" do
book = AddressBook.new
entry = book.add_entry('Ada Lovelace', '010.012.1815', 'augusta.king#lovelace.com')
book.remove_entry(entry)
expect(entry).to eq nil
end
end
AddressBook class
require_relative "entry.rb"
class AddressBook
attr_accessor :entries
def initialize
#entries = []
end
def add_entry(name, phone, email)
index = 0
#entries.each do |entry|
if name < entry.name
break
end
index += 1
end
#entries.insert(index, Entry.new(name, phone, email))
end
def remove_entry(entry)
#entries.delete(entry)
end
end
Entry class
class Entry
attr_accessor :name, :phone_number, :email
def initialize(name, phone_number, email)
#name = name
#phone_number = phone_number
#email = email
end
def to_s
"Name: #{#name}\nPhone Number: #{#phone_number}\nEmail: #{#email}"
end
end
When testing my code with RSpec I receive the following error message:
.....F
Failures:
1) AddressBook.remove_entry removes only one entry from the address book
Failure/Error: expect(entry).to eq nil
expected: nil
got: [#<Entry:0x00000101bc82f0 #name="Ada Lovelace", #phone_number="010.012.1815", #email="augusta.king#lovelace.com">]
(compared using ==)
# ./spec/address_book_spec.rb:49:in `block (3 levels) in <top (required)>'
Finished in 0.02075 seconds (files took 0.14221 seconds to load)
6 examples, 1 failure
Failed examples:
rspec ./spec/address_book_spec.rb:44 # AddressBook.remove_entry removes only one entry from the address book
Just test that the book.entries association is empty:
expect(book.entries).to be_empty
As book is a local variable in your test, you will not get a false negative result if you keep your test atomic. Some best practices on rspec.
Edit:
You can also check the entry was not in the set:
expect(book.entries.index(entry)).to be_nil
or test the change of the array length with:
expect { book.remove_entry(entry) }.to change{book.entries.count}.by(-1)
If you wonder for the be_xxx syntax sugar, if the object respond to xxx?, then you can use be_xxx in your tests (predicate matchers)
I think your expect has an issue. The entry variable is not set to nil, but the entry inside book would be nil.
I think something like this would work better:
expect(book.entries.find { |e| e.name == "Ada Lovelace" }).to eq nil
Better still, your AddressBook could have its own find method, which would make the expect param much nicer, like book.find(:name => "Ada Lovelace").
Finally, I would also put an expect call before the remove_entry call, to make sure its result equals entry.

RSpec test model and mailer

I have method in model
(User Model)
def create_reset_code
self.attributes = {:reset_code => Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )}
save(:validate=>false)
UserMailer.reset_password_email(self).deliver
end
How can I test it in RSpec? I want to test the code generation, and send e-mail
PS : using Google, but found no examples of
UPD
I write two tests:
it "should create reset code" do
#user.create_reset_code
#user.reset_code.should_not be_nil
end
it "should send reset code by email" do
#user.create_reset_code
#email_confirmation = ActionMailer::Base.deliveries.first
#email_confirmation.to.should == [#user.email]
#email_confirmation.subject.should == I18n.t('emailer.pass_reset.subject')
#email_confirmation.body.should match(/#{#user.reset_code}/)
end
But this --- #email_confirmation.body.should match(/#{#user.reset_code}/) ---- don't work
In a letter I give url with reset_code as follow reset_password_url(#user.reset_code)
FIXED
it "should send reset code by email" do
#user.create_reset_code
#email_confirmation = ActionMailer::Base.deliveries.last
#email_confirmation.to.should == [#user.email]
#email_confirmation.subject.should == I18n.t('emailer.pass_reset.subject')
#email_confirmation.html_part.body.should match /#{#user.reset_code}/
end
It's work!
Thank you all, question is closed
it "should create reset code" do
#user.create_reset_code
#user.reset_code.should_not be_nil
end
it "should send reset code by email" do
#user.create_reset_code
#email_confirmation = ActionMailer::Base.deliveries.last
#email_confirmation.to.should == [#user.email]
#email_confirmation.subject.should == I18n.t('emailer.pass_reset.subject')
#email_confirmation.html_part.body.should match /#{#user.reset_code}/
end
you can use
mail = ActionMailer::Base.deliveries.last
to get the last email sent after calling that method on the spec, then you can do specs against mail.to or mail.body.raw_source
Something like this should help you.. I have used Rspec matchers
it "should test your functionality" do
user = Factory(:ur_table, :name => 'xyz', :email => 'xyz#gmail.com')
obj = Factory(:ur_model_table)
key = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join
data = obj.create_reset_code
data.reset_code.should be(key)
let(:mail) {UserMailer.reset_password_email(user) }
mail.to.should be(user.email)
end

How do I stub out the flickraw library in my app's unit tests?

My Rails 2 app displays a slideshow of photos from Flickr via the flickraw library. My code works, but I'm stuck on how to properly write RSpec unit tests.
I have a Slide class that encapsulates everything that my app needs from flickraw. It acts somewhat like a model object but doesn't use ActiveRecord. It doesn't do much work; it delegates most of the heavy lifting to flickraw.
I haven't completed the tests because, as it is now, they require me to hard-code in some photo IDs from Flickr, and the tests would break if I rearranged my photoset or added new photos.
So my so-called unit tests are more like integration tests. I understand how to write a mock or stub using RSpec, but not sure how to do it to the flickraw library. How do I stub out flickraw and turn this into a unit test?
slide.rb:
require 'flickraw'
FlickRaw.api_key = "xxx"
FlickRaw.shared_secret = "yyy"
flickr.auth.checkToken :auth_token => "zzz"
PHOTOSET_ID = 123123123
class Slide
attr_accessor :id, :previous_id, :next_id, :url_square, :url_thumbnail, :url_small, :url_medium500,
:url_medium640, :url_large, :url_original
def self.last
photoset = flickr.photosets.getPhotos(:photoset_id => PHOTOSET_ID)
Slide.new(photoset.photo.last.id)
end
def self.first
photoset = flickr.photosets.getPhotos(:photoset_id => PHOTOSET_ID)
Slide.new(photoset.photo.first.id)
end
def self.find(id)
Slide.new(id)
end
def initialize(id)
self.id = id
photo = flickr.photos.getInfo(:photo_id => id)
context = flickr.photosets.getContext(:photoset_id => PHOTOSET_ID, :photo_id => id)
sizes = flickr.photos.getSizes(:photo_id => id)
self.previous_id = (context.prevphoto.id == 0) ? nil : context.prevphoto.id
self.next_id = (context.nextphoto.id == 0) ? nil : context.nextphoto.id
sizes.each do |size|
if size.label == "Square"
self.url_square = size.source
elsif size.label == "Thumbnail"
self.url_thumbnail = size.source
elsif size.label == "Small"
self.url_small = size.source
elsif size.label == "Medium"
self.url_medium500 = size.source
elsif size.label == "Medium 640"
self.url_medium640 = size.source
elsif size.label == "Large"
self.url_large = size.source
elsif size.label == "Original"
self.url_original = size.source
end
end
end
end
slide_spec.rb:
require 'spec_helper'
describe Slide do
before(:each) do
first_photo_id = "444555666"
#slide = Slide.new(first_photo_id)
end
describe "urls" do
it "should generate the thumbnail url" do
#slide.url_thumbnail.should match(/_t.jpg$/)
end
it "should generate the small url" do
#slide.url_small.should match(/_m.jpg$/)
end
it "should generate the medium500 url" do
#slide.url_medium500.should match(/.jpg$/)
end
it "should generate the medium640 url" do
#slide.url_medium640.should match(/_z.jpg$/)
end
it "should generate the large url" do
#slide.url_large.should match(/_b.jpg$/)
end
it "should generate the original url" do
#slide.url_original.should match(/_o.jpg$/)
end
end
describe "finding" do
it "should find the correct last photo" do
# ???
end
it "should find the correct first photo" do
# ???
end
end
describe "context" do
it "should return the correct previous photo" do
# ???
end
it "should return the correct next photo" do
# ???
end
end
end
As I understand it, you should be able to do slide.stub!(:flickr).and_return to mock out anything that isn't inside the constructor. I query the fact that the constructor is loading so much from the flickr api though.
Can you change the implementation of slide so that instead of a load of attr_accessors, you have actual methods that get stuff from the flickr api? You should be able to do this without changing the external api of the class, and you can enable speed via caching the results of api calls in instance variables.
If you still want to have all that work done in the constructor, I'd recommend having a default argument that represents the flickr service. Your constructor then becomes the following:
def initialize(id, flickr=flickr)
… complicated setup code here…
end
This way, when you're testing, just pass in a mock flickr object like so:
flickr = mock(:flickr)
#slide = Slide.new(image_id, flickr)
You can then write the usual rspec assertions against the new flickr object (again, without changing the external api).

Resources