I am having trouble setting up a spec for something that I did. I know that it is working the way I want it to because i've manually tested this, but I know if I still want a spec associated with it.
I have an index of players, and I also have a section of teams that are able to assign priority players. By doing that assigning, they pulling from the initial controller that generates the index.
For the team section I have just an ajax call where I am passing the params
ajax:
url: "#{admin_players_path(disabled: 'false')}"
dataType: 'json'
Here is the controller, I have a conditional in there that accounts for the request from the team page.
def index
#q = #player.search(params[:q])
if params[:disabled] == 'false'
#player = #q.result(distinct: true).enabled
else
#player = #q.result(distinct: true)
end
end
That is essentially all I did on my app side of things, and it works well. If I ask for the players in the normal index, it shows them to me both disabled and enabled, and if I look to assign priority players it only shows me enabled players.
My problem is revolved around my spec as I can't get things properly set up to account for that params change.
context "with a prioritized player set up" do
let(:params) { { disabled: false } }
let!(:player1) { create(:player, disabled: true, disabled_at: Time.current) }
let!(:player2) { create(:player) }
it 'returns only enabled players' do
get :index, params: params
expect(assigns[:player]).to_not include(player1)
end
end
Regardless of how I try to setup my spec, I cannot get the proper condition the way i'd like and keep getting the spec to fail. Would anyone know what I am missing with this one?
Ok so after a little work behind it, I was able to figure out what was needed within this.
In the ajax url I have the params set to disabled: 'false'
but in the spec I have the params set at let(:params) { { disabled: false } }
The problem is that in the ajax call I have things set as a string, but in the spec I have them set as a boolean.
All I had to do was change the set up to let(:params) { { disabled: ‘false' } }
and the specs were all good to go!
Related
I have a method call in a ruby model that looks like the following:
Contentful::PartnerCampaign.find_by(vanityUrl: referral_source).load.first
Within the models spec.rb file, I'm trying to mock that call and get a value by passing in a param. But I'm having trouble figuring out the correct way of calling it.
At the top of my spec.rb file I have:
let(:first_double) {
double("Contentful::Model", fields {:promotion_type => "Promotion 1"})
}
Within the describe block I've tried the following:
expect(Contentful::PartnerCampaign).to receive_message_chain(:find_by, :load, :first).
and_return(first_double)
expect(Contentful::PartnerCampaign).to receive_message_chain(:find_by, :load, :first).with(vanityUrl: 'test_promo_path').
and_return(first_double)
expect(Contentful::PartnerCampaign).to receive_message_chain(:find_by => vanityUrl: 'test_promo_path', :load, :first).
and_return(first_double)
As you can probably guess, none of these are working. Does anyone know the correct way to do this sort of thing? Is it even possible?
Generally speaking, I prefer not to use stub chains, as they are often a sign that you are violating the Law of Demeter. But, if I had to, this is how I would mock that sequence:
let(:vanity_url) { 'https://vanity.url' }
let(:partner_campaigns) { double('partner_campaigns') }
let(:loaded_partner_campaigns) { double('loaded_partner_campaigns') }
let(:partner_campaign) do
double("Contentful::Model", fields {:promotion_type => "Promotion 1"}
end
before do
allow(Contentful::PartnerCampaign)
.to receive(:find_by)
.with(vanity_url: vanity_url)
.and_return(partner_campaigns)
allow(partner_campaigns)
.to receive(:load)
.and_return(loaded_partner_campaigns)
allow(loaded_partner_campaigns)
.to receive(:first)
.and_return(partner_campaign)
end
This is what I would do. Notice that I split the "mocking" part and the "expecting" part, because usually I'll have some other it examples down below (of which then I'll need those it examples to also have the same "mocked" logic), and because I prefer them to have separate concerns: that is anything inside the it example should just normally focus on "expecting", and so any mocks or other logic, I normally put them outside the it.
let(:expected_referral_source) { 'test_promo_path' }
let(:contentful_model_double) { instance_double(Contentful::Model, promotion_type: 'Promotion 1') }
before(:each) do
# mock return values chain
# note that you are not "expecting" anything yet here
# you're just basically saying that: if Contentful::PartnerCampaign.find_by(vanityUrl: expected_referral_source).load.first is called, that it should return contentful_model_double
allow(Contentful::PartnerCampaign).to receive(:find_by).with(vanityUrl: expected_referral_source) do
double.tap do |find_by_returned_object|
allow(find_by_returned_object).to receive(:load) do
double.tap do |load_returned_object|
allow(load_returned_object).to receive(:first).and_return(contentful_model_double)
end
end
end
end
end
it 'calls Contentful::PartnerCampaign.find_by(vanityUrl: referral_source).load.first' do
expect(Contentful::PartnerCampaign).to receive(:find_by).once do |argument|
expect(argument).to eq({ vanityUrl: expected_referral_source})
double.tap do |find_by_returned_object|
expect(find_by_returned_object).to receive(:load).once do
double.tap do |load_returned_object|
expect(load_returned_object).to receive(:first).once
end
end
end
end
end
it 'does something...' do
# ...
end
it 'does some other thing...' do
# ...
end
If you do not know about ruby's tap method, feel free to check this out
I think you need to refactor the chain in two lines like this:
model = double("Contentful::Model", fields: { promotion_type: "Promotion 1" })
campaign = double
allow(Contentful::PartnerCampaign).to receive(:find_by).with(vanityUrl: 'test_promo_path').and_return(campaign)
allow(campaign).to receive_message_chain(:load, :first).and_return(model)
Then you can write your spec that will pass that attribute to find_by and check the chain.
I am trying to test the code inside a loop, how would I go about this:
class MyClass
def initialize(topics, env, config, limit)
#client = Twitter::Streaming::Client.new(config)
#topics = topics
#env = env
#limit = limit
end
def start
#client.filter(track: #topics.join(",")) do |object|
# how would I test the code inside here, basically logical stuff
next if !object.is_a?(Twitter::Tweet)
txt = get_txt(object.text)
end
end
Is there a way to do this?
If think that you can use a double of your Twitter::Streaming::Client that has a method filter and when this method is invoked it returns the desired output:
let(:client) { double 'Twitter Client', filter: twitters }
You will need to built manually the twitters object (sorry by my lack of context but I never used the Twitter client) and then you can make the assertions for the result of the start method.
As you can see, testing that code is quite tricky. This is because of the dependency on the Twitter client gem.
You can go down couple of paths:
Don't test it - the Twitter client gem should provide you with Twitter::Tweet objects. You only test your logic, i.e. get_txt method
Do what #Marcus Gomes said - create a collection double that has the filter method implemented.
What I would prefer to do is to stub the #client.filter call in the spec.
For example, in your spec:
some_collection_of_tweets = [
double(Twitter::Tweet, text: "I'll be back!"),
double(Twitter::Tweet, text: "I dare ya, I double dare ya!")
]
#my_class = MyClass.new(topics, env, config, limit)
allow(#my_class.client).to receive(:filter).and_return(some_collection_of_tweets)
This means that the some_collection_of_tweets collection will be returned every time the class calls #client.filter, and by having the data built by you, you what expectations to set.
One thing that you will have to change is to set an attr_reader :client on the class. The only side effect of this type of testing is that you are tying your code to the interfaces of the Twitter client.
But like everything else... tradeoffs :)
Hope that helps!
Perhaps you could do something like this if you really wanted to test your infinite loop logic?
RSpec.describe MyClass do
subject { MyClass.new(['foo','bar'], 'test', 'config', 1) }
let(:streaming_client) { Twitter::Streaming::Client.new }
describe '#start' do
let(:valid_tweet) { Twitter::Tweet.new(id: 1) }
before do
allow(Twitter::Streaming::Client).to receive(:new)
.with('config').and_return(streaming_client)
end
after { subject.start }
it '#get_txt receives valid tweets only' do
allow(valid_tweet).to receive(:text)
.and_return('Valid Tweet')
allow(streaming_client).to receive(:filter)
.with(track: 'foo,bar')
.and_yield(valid_tweet)
expect(subject).to receive(:get_txt)
.with('Valid Tweet')
end
it '#get_txt does not receive invalid tweets' do
allow(streaming_client).to receive(:filter)
.with(track: 'foo,bar')
.and_yield('Invalid Tweet')
expect(subject).not_to receive(:get_txt)
end
end
end
I'm writing a helper method to determine if current has any pending reviews to write. If there is a pending review, simply print a line in the view.
My helper is putting exactly the right stuff to the console, however I'm struggling with how to simply return it. In this scenario, current user has an id: 4.
My Code:
def review_pending
gallery = current_user.galleries.first
if gallery
if gallery.permissions.accepted
gallery.permissions.accepted.each do |p|
return true if p.user.reviews.find_by_reviewer_id(!current_user)
puts "already written review: #{p.user.reviews.find_by_reviewer_id(4)} - prints correctly"
end
end
end
end
My goal: if there is a user from the list that current user has not yet reviewed return true.
Thanks!!!
Thanks for all your pointers!
I had forgotten/learned 2 things to make it work:
First, if nil is returned, ruby returns the last returned value which in my case was true (if gallery.permissions.accepted).
Secondly, I placed the "!" before current_user, and should have placed it before the entire line.
Corrected Code:
def review_pending
gallery = current_user.galleries.first
if gallery
gallery.permissions.accepted.each do |p|
return !p.user.reviews.find_by_reviewer_id(current_user.id)
end
end
return false
end
I have a few RSpec controller tests. Some work, some don't, and I'm trying to figure out how on Earth to fix them up and make them more efficient
Ideally, I would like to see if I can get each spec into the following form
subject { ... }
it { ... }
it { ... }
it { ... }
Note that for all of my controller specs I've written macros for the actual controller actions. The macros are all tested and all work, and the names make it fairly obvious what they do.
My "Create" test:
formats ||= ["html", "js"]
formats.each do |format|
context "valid attributes" do
subject { do_post_create( :customer, valid_attributes, format ) }
its(:response_code) { should eq(302)}
it { should redirect_to admin_customer_path(Customer.find_by_id(???))}
it { expect { subject }.to change(Customer, :count).by(1) }
end
context "invalid attributes" do
subject { do_post_create( :customer, invalid_attributes, format ) }
its(:response_code) { should eq(200)}
it { should render_template :new }
it { expect { subject }.to_not change(Customer, :count).by(1) }
end
end
In that spec, I've been trying to figure out some way to get the ID of the newly created object from the post statement. I've tried "Customer.last", but that doesn't seem to work. Any thoughts?
My "Update" spec:
formats ||= ["html", "js"]
formats.each do |format|
context "valid attributes" do
let(:object) { FactoryGirl.create(:customer) }
subject { do_put_update( class_to_symbol(model), object.id, attributes, format ) }
its(:response_code) { should eq(302)}
it "does alter #{model}" do
do_put_update( class_to_symbol(model), object.id, attributes, format )
assigns(:customer).should eq(object)
flash[:notice].should =~ /Success/
object.reload
attributes.each do |key, value|
object.send(key.to_s).should eq(value)
end
end
end
context "invalid attributes" do
let(:object) { FactoryGirl.create("customer") }
let(:invalid_attributes) { {:username => "!"} }
subject { do_put_update( class_to_symbol(model), object.id, invalid_attributes, format ) }
its(:response_code) { should eq(200)}
it "does not alter #{model}" do
do_put_update( class_to_symbol(model), object.id, invalid_attributes, format )
assigns(:customer).should eq(object)
flash[:notice].should =~ /Fail/
object.reload
attributes.each do |key, value|
object.send(key.to_s).should_not eq(value)
end
end
end
end
In the Update test, I would like to try to express the second block in a more concise way, ideally in a way that I can use the same "subject" statement for all of the tests. Is that possible?
I think you're over-thinking these specs. Instead of trying to force every spec into a predefined format (subject/it/...) write the specs so that they clearly document what should happen, then try to refactor the code afterwards.
Case in point: the use of the implicit subject for controller actions. subject and its are meant to be used with an object, not a method, and only really make sense when used that way. So for example, this makes sense:
subject { [1, 2, 3, 4] }
its(:size) { should == 4 }
Here, it's absolutely clear what is being tested: a 4-element array has a size of 4.
However, when you write:
subject { do_post_create( :customer, valid_attributes, format ) }
its(:response_code) { should eq(302)}
it's not really clear where you are getting that response code from without inspecting the do_post_create action. You say that the names of the macros "make it fairly obvious what they do", but they don't make it fairly obvious what they will return, and this is key for using the implicit subject because it's the return value that becomes the subject.
It would be much clearer just to write:
it "responds with a 302" do
do_post_create(:customer, valid_attributes, format)
response.should eq(302)
end
I also don't recommend mixing specs with and without implicit subjects, since it makes it yet more confusing what you are actually testing. In your invalid attributes context block, for example, you set a subject, but then in your second spec you actually test assignment of customer (assigns(:customer).should eq(object)), so basically the subject is irrelevant for this test. (However by setting the subject here and then not using it you are actually sending a PUT request twice (through do_put_update), which is bound to cause problems -- again, another reason not to be making requests in a subject block.)
I could go on, but I think you get the picture. Making specs short and sweet is great if you can do it without hurting readability, but in this case I think you've gone overboard.
Just my two cents, hope it helps.
p.s. In case the views above seem a bit extreme, read the documentation for implicit subjects, where you'll see that they actually recommend against using implicit subjects at all in public-facing tests:
While the examples below demonstrate how subject can be used as a user-facing concept, we recommend that you reserve it for support of custom matchers and/or extension libraries that hide its use from examples.
I'm trying to test out a controller action on Rails 2.3.10 that connect to Google to retrieve contacts. I'm using Rspec and Mocha for testing. I want to stub out the actual call to Google since this is a unit test. I want to verify that the authsub_url method is called with the correct parameters. Stubbing the method out causes the expectation to fail.
Any advice would be appreciated.
Thanks!
My method that sets up the client to google is below:
def setup_client
#client = GData::Client::DocList.new(:authsub_scope => CONTACTS_SCOPE, :source => 'google-DocListManager-v1.1', :version => '3.0')
if params[:token].nil? && session[:google_token].nil?
#authsub_link = #client.authsub_url(import_method_gmail_url, false, true)
render :action => :index, :layout => "empty"
elsif params[:token] && session[:google_token].nil?
#client.authsub_token = params[:token]
session[:google_token] = #client.auth_handler.upgrade
end
#client.authsub_token = session[:google_token] if session[:google_token]
end
Here is my test:
describe "setup_client" do
it "has a authsub_link if there is no token parameter and the google token is not present in the session" do
GData::Client::DocList.any_instance.stubs(:authsub_url).returns("http://test.google.com/contacts")
user = Factory(:subscriber_user)
profile = Factory(:profile, :user => user)
login_as user
controller.instance_variable_get(:#client).expects(:authsub_url).with(import_method_gmail_url, false, true).once
get :index
assigns(:authsub_link).should == "http://test.google.com/contacts"
end
end
I would recommend FakeWeb. It allows you to fake web requests. Simple define the URL you're going to call and prepare the response(s). Makes your life very easy.
It looks like you are stubbing out the DocList#authsub_url method in two places :-
The first stub is on any instance of DocList and returns a URL :-
GData::Client::DocList.any_instance.stubs(:authsub_url).returns("http://test.google.com/contacts")
The second stub is on the actual instance of DocList but this returns nil because no there is no returns clause :-
controller.instance_variable_get(:#client).expects(:authsub_url).with(import_method_gmail_url, false, true).once
I think you can achieve what you want by combining them something like this :-
controller.instance_variable_get(:#client).expects(:authsub_url).with(import_method_gmail_url, false, true).returns("http://test.google.com/contacts")
Note that once is the default so is not needed unless you want to emphasise that the method should only be called once.