I have an object that saves a model and runs a background job.
Class UseCase
...
def self.perform
#account.save
BackgroundJob.perform_later(#account.id)
end
end
In my spec I'd like to test separately that both messages are sent.
I started with something like
it 'saves the account' do
expect_any_instance_of(Account).to receive(:save)
UseCase.perform(account)
end
And this worked fine when I was just saving the account in the perform.
But when I have added the background job the spec doesn't pass anymore since now Couldn't find Account without an ID.
How can I verify (in RSped 3.5) separately that both messages are sent?
UPDATE
it 'runs the job' do
expect(BackgroundJob).to receive(:perform_later).with(instance_of(Fixnum))
UseCase.perform(account)
end
passes so I suppose the account is correctly saved.
However, when I try to inspect #account
def self.perform
#account.save
byebug
BackgroundJob.perform_later(#account.id)
end
In 'saves the account', I get
(byebug) #account
#<Account id: nil, full_name: "john doe" ...>
In 'runs the job', I get
(byebug) #account
#<Account id: 1, full_name: "john doe" ...>
The expectation makes #account a test double so in the first spec the job cannot get the id.
Thanks
The error Couldn't find Account without an ID is actually pretty helpful considering the code that you have inside your perform method.
The issue is mentioned in the comments but I'll elaborate a bit further.
You are using #account.save (I'm assuming #account is an ActiveRecord object) which by definition will return true/false when run (see documentation)
What you probably want is to use save! instead since it will raise a ActiveRecord::RecordInvalid error and stop execution rather than triggering the error you noted earlier. (toss a binding.pry into the method and note what #account is when attempting to call .id)
When you change to save! you can add a test for a case where save might fail (missing attribute, etc). Might look something like this
it 'should raise error when trying to save invalid record' do
# do something to invalidate #account
#account.username = nil
expect { UseCase.perform(#account) }.to raise_error(ActiveRecord::RecordInvalid)
#confirm that no messages were sent
end
Hope this helps you out! GL and let me know if you have any questions / need more help with rspec
Related
Hi I want to know how can I write rspec for the following
def find_user(content)
user = User.find(content.to_i) ||
User.find(email: content) rescue
nil
end
I tried writing
It "user with user name" do
expect(User).to receive(:find).with(email: "test#a.com").and_return(user)
End
But I am gettig error saying
Argument Error
Block not Passed
Can someone please tell what am i missing
I may look first at your code here.
def find_user(content)
user = User.find(content.to_i) ||
User.find(email: content) rescue
nil
end
What is content? I looks like you're expecting either a user_id or an email address.
Doing this from the console:
irb(main):080:0> User.find("email#email.com".to_i)
=> ActiveRecord::RecordNotFound (Couldn't find User with 'id'=0)
So it seems as if having a generic find_user method may be contributing to some of the test writing confusion.
Many times, overly complex tests point to overly complex code.
Perhaps you need one method
find_user_by_email(email)
and another
find_user_by_id(id)
Also, refer to https://api.rubyonrails.org/v6.1.3.2/classes/ActiveRecord/FinderMethods.html#method-i-find_by
It will automatically return nil if nothing is found.
Start there, And then like the other commenters, then post your class, and the spec and we can go from there.
I am running into an error when writing an rSpec Capybara test to mock a user signing up for the website.
It should be noted that, unfortunately, I am writing a test for a codebase that is entirely new to me, so a lot of the code for the main program is unknown to me. If asked for something I will try and dig up the relevant code, but I'm not certain what else to include at the moment. However I can say it has been successfully running in production for a while, and I can manually test it successfully - so I think the error is probably in my test, or perhaps some configuration used just while testing.
rspec test
RSpec.describe 'New User Sign Up', type: :feature do
scenario 'valid signup inputs' do
visit("/users/sign_up")
fill_in 'user_name', with: 'TEST'
fill_in 'user_email', with: 'TEST#test.com'
fill_in 'user_username_with_caps', with: "TEST"
fill_in 'user_password', with: 'TESTpw123'
puts find_field('user_email').value
puts expect(find_field('user_email').value).to eq 'TEST#test.com'
puts expect(page).to have_selector("input[value='TEST#test.com']")
puts page.should have_field('user_email', with: 'TEST#test.com')
click_on 'Create an account'
end
end
The output is:
bgc#jadzia:~/Documents/Work/BadgeList/code/badgelist/backend/spec$ bundle exec rspec sign_up_spec.rb
TEST#test.com
true
true
true
F
Failures:
1) New User Sign Up valid signup inputs
Failure/Error: click_on 'Create an account'
Mongoid::Errors::Validations:
message:
Validation of User failed.
summary:
The following errors were found: Email can't be blank
resolution:
Try persisting the document with valid data or remove the validations.
Note the puts statements and the corresponding output before the error message.
As far as I can tell, the fields ARE getting filled in properly. However, somehow this does not get recognized when it attempts to complete the sign up. The error is coming from Mongoid, so somehow mongo reacts differently to an testing auto-entered field vs. a manually entered one.
It should also be noted that, if I disable database_cleaner-mongoid, and run the same test twice... I get a -DIFFERENT- outcome. The test technically passes, but there is a warning prompt on the page that says "This email is taken".
So, somehow the value in the email field is...
Being entered/read properly when directly querying the field value on the page
Not recognized when it immediately afterwards tries to use that value to Create an account, instead the field is seen as blank.
But ALSO the field is successfully saved into the DB for a new account with this information, so running the same test again creates a conflict with the entry from the previous test if the DB is not cleaned first.
All click_on does is click on the button. It doesn't wait for the server to do anything, or anything on the page to change, etc. So ending the test with click_on isn't actually testing for any behavior, and the DB is going to get reset while the action triggered by click_on is still occurring, which is unlikely to be what you want. You need to add an expectation after the click_on testing for what you expect to see in the page like
...
click_on 'Create an account'
expect(page).to have_text('User created!!!)
Also note that the expectations you are calling puts on aren't actually defined to return anything specific, so the fact you're seeing true really is just luck. They're defined to not raise an error when successful, and raise an error when not.
In short, I want to raise an exception via a stubbed method, but only if the object that has the stubbed method has a particular state.
Mail::Message.any_instance.stub(:deliver) do
if to == "notarealemailaddress!##!##"
raise Exception, "SMTP Error"
else
return true
end
end
This doesn't work, because the context inside the stub block is: RSpec::Core::ExampleGroup::Nested_1::Nested_2::Nested_2.
How do I get access to the stubbed object?
using ruby 2, rspec 2.
The actual scenario is I have an app that sounds out thousands of emails in batches and I have code that catches SMTP exceptions, logs the batch, and proceeds. So I want to test sending several batches, where one of the batches in the middle throws an exception.
It looks like this is solved in the latest(currently alpha) version of Rspec v3:
https://github.com/rspec/rspec-mocks/commit/ebd1cdae3eed620bd9d9ab08282581ebc2248535#diff-060466b2a68739ac2a2798a9b2e78643
it "passes the instance as the first arg of the implementation block" do
instance = klass.new
expect { |b|
klass.any_instance.should_receive(:bees).with(:sup, &b)
instance.bees(:sup)
}.to yield_with_args(instance, :sup)
end
I believe you specify the arguments using the with method, so in your case it would be something along the lines of:
Mail::Message.any_instance.stub(:deliver).with(to: "notarealemailaddress!##!##") do
raise Exception, "SMTP Error"
end
There's full documentation here:
https://www.relishapp.com/rspec/rspec-mocks/v/2-3/docs/method-stubs
Ok, here's how you can get this behavior fairly easily without upgrading:
class Rspec::Mocks::MessageExpectation
# pulling in behavior from rspec v3 that I really really really need, ok?
# when upgrading to v3, delete me!
def invoke_with_orig_object(parent_stub, *args, &block)
raise "Delete me. I was just stubbed to pull in behavior from RSpec v3 before it was production ready to fix a bug! But now I see you are using Rspec v3. See this commit: https://github.com/rspec/rspec-mocks/commit/ebd1cdae3eed620bd9d9ab08282581ebc2248535#diff-060466b2a68739ac2a2798a9b2e78643" if RSpec::Version::STRING > "2.99.0.pre"
args.unshift(#method_double.object)
invoke_without_orig_object(parent_stub, *args, &block)
end
alias_method_chain :invoke, :orig_object
end
Drop that at the bottom of your spec file. You'll notice I even add a check to raise an error once RSpec is upgraded. boom!
I'll be brief with the code samples, as all of my tests pass except the one below. I got it to pass by changing things up a bit, but I'm not sure why version 1 fails and version 2 works.
My model:
# app/models/person.rb
class Person
validates :contact_number, uniqueness: true
end
Model spec
# spec/models/person_spec.rb
require 'spec_helper'
describe Person do
it 'is a valid factory' do
create(:person).should be_valid # passes
end
it 'has a unique phone number' do
create(:person)
build(:person).should_not be_valid # fails
end
it 'also has a unique phone number' do
person1 = create(:person)
person2 = person1.dup
person2.should_not be_valid # passes
end
end
As far as I can tell, the two uniqueness tests should be doing the same thing, however one passes and one fails.
If it matters, I am using mongoid, though I don't think that should have any effect. I'm also not doing anything with nested contexts or describes in my test, so I think the scope is correct. Any insight is appreciated.
UPDATE 1: I realized in my factory I am adding an initialize_with block like this:
initialize_with { Person.find_or_create_by(contact_number: contact_number) }
I realized that this may be the reason the validation was failing -- I was just getting the same person back. However, commenting out that line gives the following error:
Mongoid::Errors::Validations:
Problem:
Validation of Person failed.
Summary:
The following errors were found: Contact number is already taken
Resolution:
Try persisting the document with valid data or remove the validations.
Which, in theory is good, I suppose, since it won't let me save a second person with the same contact number, but I'd prefer my test to pass.
Probably your person factory has a sequence in contact_number making a diferent contact_number in each person.
Just realize that the build(:person) doesnt validate. The validation occurs only in create.
I strongly suggest use of shoulda-matchers for this kind of validations.
It is possible that your database is being cleaned (do you have database-cleaner in your Gemfile), or your tests are not being run in the order you think they are. (Check for :random in your spec_helper.rb)
While the above answer regarding using shoulda-matchers will help you run this particular test in RSpec more concisely, you probably want to have your unique phone number test be able to be run completely on its own without relying on another spec having executed. Your second test is an example of Obscure Test (and also a little bit of Mystery Guest http://robots.thoughtbot.com/mystery-guest), where it's not clear from the test code what is actually being tested. Your phone number parameter is defined in another file (the factory), and the prior data setup is being run in another spec somewhere else in the file.
Your second test is already better because it is more explicitly showing what you're testing, and doesn't rely on another spec having been run. I would actually write it like this to make it more explicit:
it 'has a unique phone number' do
person1 = create(:person, phone_number: '555-123-4567')
person2 = create(:person, phone_number: '555-123-4567')
# can use 'should' here instead
expect(person2).not_to be_valid
end
If you don't explicitly make it about the phone number, then if you change your factory this test might start failing even though your code is still sound. In addition, if you have other attributes for which you are validating uniqueness, your previous test might pass even though the phone number validation is missing.
I figured it out! On a whim, I checked out the test database and noticed that a Person object was lingering around. So, it actually wasn't the
build(:person).should_not be_valid that was raising the Mongoid exception. It was the create call on the line before. Clearing out the DB and running the spec again passed, but again the data was persisting. I double checked my spec_helper.rb file and noticed I wasn't calling start on the DatabaseCleaner. My updated spec_helper.rb file looks like this, and now everything works:
# Clean up the database
require 'database_cleaner'
config.mock_with :rspec
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.orm = "mongoid"
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
I have a very basic problem for which I am not able to find any solution.
So I am using Watir Webdriver with testunit to test my web application.
I have a test method which I would want to run against multiple set of test-data.
While I can surely use old loop tricks to run it but that would show as only 1 test ran which is not what I want.
I know in testng we have #dataprovider, I am looking for something similar in testunit.
Any help!!
Here is what I have so far:
[1,2].each do |p|
define_method :"test_that_#{p}_is_logged_in" do
# code to log in
end
end
This works fine. But my problem is how and where would I create data against which I can loop in. I am reading my data from excel say I have a list of hash which I get from excel something like
[{:name =>abc,:password => test},{:name =>def,:password => test}]
Current Code status:
class RubyTest < Test::Unit::TestCase
def setup
#excel_array = util.get_excel_map //This gives me an array of hash from excel
end
#excel_array.each do |p|
define_method :"test_that_#{p}_is_logged_in" do
//Code to check login
end
end
I am struggling to run the loop. I get an error saying "undefined method `each' for nil:NilClass (NoMethodError)" on class declaration line
You are wanting to do something like this:
require 'minitest/autorun'
describe 'Login' do
5.times do |number|
it "must allow user#{number} to login" do
assert true # replace this assert with your real code and validation
end
end
end
Of course, I am mixing spec and test/unit assert wording here, but in any case, where the assert is, you would place the assertion/expectation.
As this code stands, it will pass 5 times, and if you were to report in story form, it would be change by the user number for the appropriate test.
Where to get the data from, that is the part of the code that is missing, where you have yet to try and get errors.