I want to stub set_user_tokens which is executed on the initialized (not saved) ActiveRecord object. This method assigns a token to the login object.
class AwareLogin < Authenticatable
def authenticate!
login = Login.find_or_initialize_for_authentication(params[:login][:email], 'aware')
class << login
include AwareAuth
end
if login.valid_password?(password) || (set_token = login.id.nil? && aware_response.success?)
login.set_user_tokens(aware_response) if set_token
success!(login)
else
aware_response.success? ? fail!(:aware_auth) : raise(ActiveRecord::Rollback)
end
end
end
end
So I want to stub setu_user_tokens method:
login.set_user_tokens(aware_response) if set_token
to receive login ActiveRecord object with attributes of oauth_tokens like below:
login.oauth_token
=> {"access_token" => return_token,"refresh_token" => nil,"token_expiration" => 1200 }
I've tried:
allow_any_instance_of(Login).to receive(:set_user_tokens).with(status: 200, body: { access_token: return_token }.to_json, success?: true).and_return(
oauth_tokens: {
"access_token" => return_token,
"refresh_token" => nil,
"token_expiration" => 1200 },
)
But I'm getting an error:
Login does not implement #set_user_tokens
I would be willing to bet your issue is set_user_tokens is part of AwareAuth.
Since you are only including this module in the eigenclass of the instance (login) as part of the AwareLogin#authenticate! method, the Login class does not implement that method at any point in time.
Is there a reason you are doing it this way rather than just including AwareAuth in the Login class in the first place?
Either way, while your question appears to lack context for the test itself, if I understand correctly, we should be able to resolve these issues as follows:
it 'sets user tokens' do
login = Login
.find_or_initialize_for_authentication('some_email#example.com', 'aware')
.tap {|l| l.singleton_class.send(:include, AwareAuth) }
allow(Login).to receive(:find_or_initialize_for_authentication).and_return(login)
allow(login).to receive(:set_user_tokens).and_return(
oauth_tokens: {
"access_token" => return_token,
"refresh_token" => nil,
"token_expiration" => 1200 }
)
#perform your action and expectations here
end
By using partial doubles you can stub the specific methods you need to without impacting any other functionality of the object itself.
Related
In other words: It there a chance for a class modification (on tests) to affect production code?
(This code example is using Rspec for testing in a Rails app)
My controller example
In this controller ExternalModel is created. Then it's "inscription" method is called and the results are assigned to a variable. It uses the result for other actions on the controller method.
class ExampleController < ApplicationController
def callback_page
external_model = ExternalModel.new(argument)
result = external_model.inscription
render_error_json && return unless result['error_desc'].eql? 'OK'
TransactionModel.create(token: result['token'])
end
end
My Spec example
In the spec I modify ExternalModel so it returns what I want when calling the .inscription method:
ExternalModel.class_eval {
def inscription(_fake_arguments)
{
'error_desc' => 'OK',
'token' => '1234'
}
end
}
This is the entire spec:
RSpec.describe 'Example management', type: :request do
context 'callback_page' do
it 'creates a transaction' do
ExternalModel.class_eval {
def inscription(_fake_arguments)
{
'error_desc' => 'OK',
'token' => '1234'
}
end
}
expect {
post(callback_page_path)
}.to change(TransactionModel.all, :count).by(1)
expect(response).to render_template(:callback_page)
end
end
end
What you're trying to achieve here is exactly what stubs are for: They're effectively a way to fake behavior within the scope of a single example that then automatically resets to its original behavior after the example has run.
In your example, this would look roughly like this:
allow_any_instance_of(ExternalModel).
to receive(:inscription).
and_return({ 'error_desc' => 'OK', 'token' => '1234' })
More details can be found in the docs for the rspec-mocks gem: https://relishapp.com/rspec/rspec-mocks/v/3-9/docs.
I am trying to sending emails using MailGun's batch sending API using MailGun ruby sdk(https://github.com/mailgun/mailgun-ruby/blob/master/docs/MessageBuilder.md). As of now I have this method inside a class which inherits from ActionMailer.
class BatchMailer < ApplicationMailer
def send_batch_email(mail, recipients)
# First, instantiate the Mailgun Client with your API key
mg_client = Mailgun::Client.new("your-api-key")
# Create a Batch Message object, pass in the client and your domain.
mb_obj = Mailgun::BatchMessage.new(mg_client, "example.com")
# Define the from address.
mb_obj.from("me#example.com", {"first" => "Ruby", "last" => "SDK"});
# Define the subject.
mb_obj.subject("A message from the Ruby SDK using Message Builder!");
# Define the body of the message.
mb_obj.body_text("This is the text body of the message!");
# Loop through all of your recipients
mb_obj.add_recipient(:to, "john.doe#example.com", {"first" => "John", "last" => "Doe"});
mb_obj.add_recipient(:to, "jane.doe#example.com", {"first" => "Jane", "last" => "Doe"});
mb_obj.add_recipient(:to, "bob.doe#example.com", {"first" => "Bob", "last" => "Doe"});
...
mb_obj.add_recipient(:to, "sally.doe#example.com", {"first" => "Sally", "last" => "Doe"});
# Call finalize to get a list of message ids and totals.
message_ids = mb_obj.finalize
# {'id1234#example.com' => 1000, 'id5678#example.com' => 15}
end
end
Is is a correct way to keep the method that doesn't use actionmailer to send emails inside mailer?
ActionMailer method returns mail object but when trying to write spec for the method that uses API to send emails I can't able to get response as there won't be a mail object(ActionMailer message object). Where to keep this method and how it can be tested?
Is this a correct way to keep the method that doesn't use actionmailer to send emails inside mailer?
There is no reason to use a Mailer in this case. Simply use a service object (a plain-old ruby object or PORO). It might look something like:
class BatchMailerService
attr_accessor *%w(
mail
recipients
recipient
).freeze
delegate *%w(
from
subject
body_text
add_recipient
finalize
), to: :mb_obj
delegate *%w(
address
first_name
last_name
), to: :recipient, prefix: true
class << self
def call(mail, recipients)
new(mail, recipients).call
end
end # Class Methods
#==============================================================================================
# Instance Methods
#==============================================================================================
def initialize(mail, recipients)
#mail, #recipients = mail, recipients
end
def call
setup_mail
add_recipients
message_ids = finalize
end
private
def mg_client
#mg_client ||= Mailgun::Client.new(ENV["your-api-key"])
end
def mb_obj
#mb_obj ||= Mailgun::BatchMessage.new(mg_client, "example.com")
end
def setup_mail
from("me#example.com", {"first" => "Ruby", "last" => "SDK"})
subject("A message from the Ruby SDK using Message Builder!")
body_text("This is the text body of the message!")
end
def add_recipients
recipients.each do |recipient|
#recipient = recipient
add_recipient(
:to,
recipient_address,
{
first: recipient_first_name,
last: recipient_last_name
}
)
end
end
end
Which you would use something like:
BatchMailerService.call(mail, recipients)
(assuming, naturally, that you have variables called mail and recipients).
Where to keep this method?
You might place that file in app/services/batch_mailer_service.rb.
How can it be tested?
What do you mean? How you test the service depends on what your criteria for success are. You could test that mb_obj receives the finalize call (maybe using something like expect().to receive). You could test message_ids contains the correct information (maybe using something like expect().to include). It sort of depends.
I am looking to understand how I can access a variable set in method A, then use that variable in method B, and also a clean way of reusing the same part of code and then only changing the query
require 'google/api_client'
module GoogleAnalytics
class Analytic
SERVICE_ACCOUNT_EMAIL_ADDRESS = ENV['SERVICE_ACCOUNT_EMAIL_ADDRESS']
PATH_TO_KEY_FILE = ENV['PATH_TO_KEY_FILE']
PROFILE = ENV['ANALYTICS_PROFILE_ID']
def google_analytics_api
client = Google::APIClient.new(
application_name: "Example Application",
application_version: "1.0")
client.authorization = Signet::OAuth2::Client.new(
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
:audience => 'https://accounts.google.com/o/oauth2/token',
:scope => 'https://www.googleapis.com/auth/analytics.readonly',
:issuer => SERVICE_ACCOUNT_EMAIL_ADDRESS,
:signing_key => Google::APIClient::KeyUtils.load_from_pkcs12(PATH_TO_KEY_FILE, 'notasecret')).tap { |auth| auth.fetch_access_token! }
api_method = client.discovered_api('analytics','v3').data.ga.get
# make queries
result = client.execute(:api_method => api_method, :parameters => {
'ids' => PROFILE,
'start-date' => Date.new(2014,1,1).to_s,
'end-date' => Date.today.to_s,
'dimensions' => 'ga:pagePath',
'metrics' => 'ga:pageviews',
'filters' => 'ga:pagePath==/'
})
end
end
end
So if i run the method google_analytics_api i get a set of results returned assigned to the variable result.
So what if i want another 2 separate methods that will return different sets of results, so
new users and bounce rates, that would be two separate calls changing the request params wouldnt it? would i have to repeat the whole method?
Is there a way to refactor this so that the authorization call can be wrapped up in its on method and all i change is the request params assigned to result ?
So something like this
def api_call
logic to make request
end
def new_users
api_call
# make queries
result = client.execute(:api_method => api_method, :parameters => {
'ids' => PROFILE,
'start-date' => Date.new(2014,1,1).to_s,
'end-date' => Date.today.to_s,
'dimensions' => 'ga:pagePath',
'metrics' => 'ga:newUsers',
'filters' => 'ga:pagePath==/'
})
end
One of the problems though will be having the local variables client and result available in the new_users method, what could these be changed to? an instance variable with an #? or a class variable with an ## ?
Your instincts are good - you don't want to repeat yourself, and there are better ways of structuring this code. But rather than sharing variables, you should think about small pieces, loosely joined. Write methods that do one thing well, and combine them together. For instance, we could write a get_client method that just returns a client for other methods to use:
protected
def get_client
client = Google::APIClient.new(
application_name: "Example Application",
application_version: "1.0")
client.authorization = Signet::OAuth2::Client.new(
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
:audience => 'https://accounts.google.com/o/oauth2/token',
:scope => 'https://www.googleapis.com/auth/analytics.readonly',
:issuer => SERVICE_ACCOUNT_EMAIL_ADDRESS,
:signing_key => Google::APIClient::KeyUtils.load_from_pkcs12(PATH_TO_KEY_FILE, 'notasecret')).tap { |auth| auth.fetch_access_token! }
client
end
It's protected because external code - stuff outside your Analytic class - shouldn't work with it directly. They should use the methods we provide for them.
You could also provide a helper method for getting results from the API. I'm not familiar with the query API, but it looks like it's your metrics value that changes. So maybe something like this:
protected
def get_result(metrics)
client = self.get_client
api_method = client.discovered_api('analytics','v3').data.ga.get
result = client.execute(:api_method => api_method, :parameters => {
'ids' => PROFILE,
'start-date' => Date.new(2014,1,1).to_s,
'end-date' => Date.today.to_s,
'dimensions' => 'ga:pagePath',
'metrics' => metrics,
'filters' => 'ga:pagePath==/'
})
result
end
Now you can write simple methods that your external classes can use:
def new_users
get_result('ga:newUsers')
end
def total_visits
get_result('ga:pageViews')
end
If you can, try to return simple data from these methods. Maybe total_visits is going to return get_result('ga:pageViews')['totalsForAllResults']['ga:pageviews'] instead. Code outside your class shouldn't have to know about the GA data format to work with it. This is called encapsulation.
From talking on Skype, I think there are several things to look at
Init
Currently, you're using the google_analytics_api method every time you want to use the module. This is completely inefficient, and is partly why you have this issue now. Instead, I would create an init method, which will fire each time you initialize the object (and make GoogleAnalytics into a class of its own):
#lib/google_analytics.rb
Class GoogleAnalytics
def initialize
... google_analytics_api method here
end
end
This will allow you to treat your current module as a real Ruby object - like this:
#analytics = GoogleAnalytics.new #-> fires initialize method
This will give you the ability to call the object (which will pull the data from the API), and then split that data accordingly, for the different use cases you have.
Instance Methods
This leads me nicely onto the idea of instance methods
What you're referring to, and indeed what Alex P is referring to, is the idea of an instance method. This doubles as an attribute for an object, but essentially allows you to call a piece of functionality on an instance of a method.
So in Alex's example, you have:
def new_users
get_result('ga:newUsers')
end
This is just calling an instance method of your class:
GoogleAnalytics::Analytic.new_users
This will create an instance of the Analytic class, and then call the new_users method (which should be a class method). This method will then allow you to call instance methods on the newly initialized object, hence the get_result method call
--
What I'm proposing is to use instance methods after the object has been initialized, giving you acesss to the data defined with google_analytics_api
For example:
#app/controllers/analyics_controller.rb
Class AnalyticsController < ApplicationController
def index
#analytics = GoogleAnalytics.new
#new_users = #analytics.new_users
end
end
#lib/google_analytics.rb
Class GoogleAnalytics
def initialize
... google_analytics_api method here
end
def new_users
return [new_users_data]
end
end
The one caveat to this is whether this will work without the module. I think it should, but it's untested for me
I've external API endpoint, let's say: http://www.fake_me_hard.com/api. I would like to make some calls to this from my app.
Endpoint accepts following structure as argument:
{
:amount => amount,
:backurl => root_path,
:language => locale,
:orderid => order_id,
:pm => payment_method,
:accept_url => "/payment/success",
:exception_url => "/payment/failure",
}
For collecting this hash is responsible method EndpointRequestCollector.give_me_hash.
How I should test if give_me_hash returns proper structure ?
I can use the same strategy for creating this structure in specs and class as well so:
class EndpointRequestsCollector
def self.give_me_hash
{
#....collecting hash #1
}
end
end
describe EndpointRequestCollector do
context '.give_me_hash' do
it 'returns proper structure' do
expect(described_class.give_me_hash).to eq(
{
#... collecting hash #2
}
)
end
end
end
...but it would be repeating the same code in 2 places, and won't test anything.
Do you know any good approach to this problem ?
This is the way that i usually test my json api's:
If you just want to test the format, you can use include matcher:
%w(my awesome keys).each do |expected_key|
expect(described_class.give_me_hash.keys).to include(expected_key)
end
By doing this, you have the guarantee that the formar is correct, until someone break you method.
If you want to test the returned values, you can use something like that:
let(:correct_value) { 42 }
it 'must have correct value' do
expect(described_class.give_me_hash[key]). to eq correct_value
end
But i recomment you to separate this the logic to get the value to another method, and make another test just for it.
Perhaps:
let(:args) {["amount", "backurl", "language", "orderid", "pm", "accept_url", "exception_url"]}
#...
it 'returns proper structure' do
described_class.give_me_hash.each_key do |key|
expect(key).to satisfy{|key| args.include?(key)}
end
end
I have been using ruby to make API calls and operating strictly in the terminal for some time. I am now in the process of learning more about rails and trying to get out of my terminal. How can I, using rails 4.0, put a variable to the screen from an already existing .rb file? I am confused as to where I should write the API request to get the variable- Is it a controller, can I write it directly in a view, etc.
Sample idea:
#test.rb
call= "/api/v2/surveys/"
auth = {:username => "test", :password => "password"}
url = HTTParty.get("https://surveys.com#{call}",
:basic_auth => auth,
:headers => { 'ContentType' => 'application/json' } )
response = JSON.parse(url.body)
survey_ids = response["surveys"].map { |s| s["id"] }
survey_ids.each do |i|
puts i
end
That is a sample .rb script I already have. The difference is I would like for puts i to happen on a web app when a page is loaded instead of me running the script in my terminal. What would I use in rails to make that happen?
It depends entirely on how your application is going to be set up but here's a basic example:
Say you have a Survey model:
class Survey < ActiveRecord::Base
attr_accessible :survey_id
end
You can place your call for a list of surveys (I'm assuming that's what your code does) in the SurveysController:
class SurveysController < ApplicationController
def index
#surveys = Survey.all
end
def show
#survey = Survey.find(params[:id])
end
def pull_surveys
call= "/api/v2/surveys/"
auth = {:username => "test", :password => "password"}
url = HTTParty.get("https://surveys.com#{call}",
:basic_auth => auth,
:headers => { 'ContentType' => 'application/json' } )
response = JSON.parse(url.body)
survey_ids = response["surveys"].map { |s| s["id"] }
survey_ids.each do |i|
Survey.create(survey_id: i)
end
end
After calling the pull_surveys method, you'll actually have surveys your view can load so in your views for the Survey Model you can use #surveys or #survey (depending on which view you're in) and serve up whatever you want (e.g #survey.survey_id in show to show that specific survey's ID).
Note that you'll want to be careful about where you place your API call methods - I placed it in the controller for simplicity's sake but you may not want to do this.
There's lots of useful info in the rails guides to get you started: http://guides.rubyonrails.org/index.html