I have an action in my controller that connects to a third party api and does some work. If the code succeeds then my user is saved. If the third party api fails, then the user is not saved.
def create
if ThirdPartyApp.connect.eql? true
User.create(params[:user])
else
redirect home_path
end
end
I want to test the User.create code without having to worry about the third party app code. I'm aware of stubbing and mocking and understand the difference in that one tests instance methods and the other class methods. I'm unsure how to use this though in my rspec test as certain conditions can't be met in the test that are required for the third party app to work. Any help on how I can write this test with stubbing, mocking, or some other method would be great.
There are a few solutions here:
First, you could use a tool like VCR (Ryan Bates has a great screencast on this!) That way, you run the test the first time with the response from the API being recorded so that from the second time onwards it just 'replays' the 'recording' to use their terms.
Using stubbing/mocking on a request that is so dependant on an API response can be hard. If it were me (and I am guessing at how you are testing this controller action)
it "creates user" do
ThirdPartyApp.any_instance.stub(:connect) { true }
# fill in form or submit params as required
User.count.should == 1 # or some other assertion
end
Then you can test the fail being:
it "fails from the api" do
ThirdPartyApp.any_instance.stub(:connect) { false }
# fill in form or submit params as required
current_path.should == '/whatever' # or some other assertion
end
Others may argue a different solution, and if I had the choice in this circumstance I would go with VCR.
Related
Let's say I have a database to store the data of people (name, email). And there's an update method in the controller. The method is like:
def update
#people = People.find(params[:id])
if #people.update(people_params)
redirect_to people_path
else
render 'edit'
end
end
I wonder how can I test the situation that the update failed? Or do I really need to test it? I have searched it on StackOverflow, there is a link about it, but it not says if I should or should not to test it. Could anyone give me some help about it? If it can be test, how? Thank you so much!
You don’t need to test Ruby and Rails internals. Either you trust both of them do work as expected, or you’d better switch to some other language / framework.
Whether you still want to test it, mock everything unrelated. Here is an example of doing this with rspec and flexmock.
describe '#update' do
let(:person) { build(:people) }
before do
flexmock(People).should_receive(:find).once.returns person
flexmock(person).should_receive(:update).once.returns false
end
it 'redirects to `edit` page' do
...
end
end
Typically the update failure would be due to object to be saved not being valid, according to its validation criteria. You would normally test the validation criteria in the model specs, but when testing the controller, or in integration tests, you might want to ensure that the #edit render occurs when the user tries to save an invalid model.
If the model does not have any validation criteria, you can probably skip the else render :edit altogether. It's part of the Rails scaffold that may not apply in all cases.
You can test your update fail scenario by trying to save an invalid model. You would typically confirm that the user was correctly informed of the validity problem (flash message).
There's no right or wrong as to whether or not to test. Personally, I would test it, b/c I like TDD, and I prefer to over-test rather than under-test. Many would not.
Scenario:
We use capybara integration tests to test that our frontend plumbing (javascript) is connected properly.
Sometimes all we need to validate the test is:
has content rendered properly on the page
has the js called the correct url open interaction
Problem:
Item 1 above is easy. However, with item 2 I can't seem to find an easy way to say:
Assert that url was called from js in browser.
Example:
it "should call coorect url with correct query string" do
visit widgets_path
# THIS IS WHAT I NEED TO KNOW
expect(something).to receive(:some_method).with(url: "my/test/url", params: {per_page: 2})
# In other words, I don't want the controller action to run. I don't care about the result since the controller is being tested elsewhere.
# I just need to know that the correct URL was called with the correct params.
within 'ul.pagination' do
click_on '2'
end
end
I've tried mocking the controller action, but there's no way to inspect the params. Or is there? Without inspecting the params, how can I know if the correct stuff was sent? All I know it's the correct route, which isn't enough.
If I could inspect the params then this would be solved... but otherwise?
If you are looking for the Rails solution, here it is! Tested with Rails 5.1.3.
1) Create a request params matcher spec/support/matchers/request_with_params.rb
RSpec::Matchers.define :request_with_params do |params|
match { |request| request.params.symbolize_keys.slice(*params.keys) == params }
end
2) Create a helper method for your acceptance tests (you can use some logics to pass symbol instead of class UsersController -> :users if needed)
def expect_request(controller, action, params = {})
expect_any_instance_of(ActionDispatch::Routing::RouteSet::Dispatcher)
.to receive(:dispatch)
.with(controller, action.to_s, request_with_params(params), anything)
end
end
3) Use it!
expect_request(UsersController, :index)
or with params
expect_request(UsersController, :show, { id: 1 })
OR
4) There is another way in using https://github.com/oesmith/puffing-billy Check this gem for intercepting requests sent by your browser. But it can be an overkill if you need to mock only certain requests to your backend app.
Capybara integration tests intentionally don't support that. They are end-to-end blackbox tests, shouldn't generally be mocked, and really only support checking for things visible to the user in the browser. In your example case that would mean expecting on whatever visible change is caused by the JS call to the specific URL. Something like
expect(page).to have_css('div.widget', count: 2)
I created an RSpec spec to test if a POST #create action works properly:
describe "POST #create" do
it "creates a career" do
expect {
post "/careers/", career: attributes_for(:career)
}.to change(Career, :count).by 1
end
end
The above code works correctly. The issue happens when I create another test to allow only users with roles of "admin". Do I need to create a new user, log them in, and then run the above test? Do I need to do this for all future tests which have a restriction based on the user's role?
Is there another way to do this type of testing? 1) Just test if the create method works, and 2) only allow Users with "admin" role access the GET #new and POST #create methods?
When your feature is fully developed you'll want to have the following tests:
one happy-path test in which an admin creates a career
one in which a non-admin tries to create a career but is prevented from doing so
possibly another in which a not-logged-in user tries to create a career but is prevented from doing so (whether you want this depends on whether you have to write different code to handle non-logged-in and logged-in non-admin users), and
possibly other tests of different scenarios in which an admin creates a career.
This idea of having one complete, happy-path test is one of the most fundamental patterns in testing, but I'm not aware that it has a name, other than being implied by the term "happy path".
It looks like you're doing TDD. Great! To get from where you are now to the above list of tests, the next test to write is the one where the non-logged-in user is prevented from creating a career. To make both tests pass at the same time you'll need to change the first test to log in an admin. And if you need more tests of successfully creating a career (bullet 4), yes, you'll need to log in an admin in those too.
Side notes:
Unless you already have it, I'd write your happy-path spec not as a controller spec but as a feature spec (an acceptance test), so that you specify the important parts of the UI and integration-test the entire stack. Your failed-authentication specs might work as controller specs, although you might decide you need to acceptance-test the UI when a user doesn't have permissions for at least one of those scenarios.
I really don't like that expect {}.to change syntax. It prevents you from making any other expectations on the result of posting. In your example I would want to expect that the HTTP response status is 200 (response.should be_success). As I said, though, my first spec would be a feature spec, not a controller spec.
So, this is an interesting question. Yes, you should definitely (IMO) test authentication separately from the target method/action. Each of these constitutes a unit of functionality and should be tested as such.
In my current project, I'm favoring POROs (I often keep them in a directory called 'managers' although I know many people prefer to call them 'services') for all sorts of things because it lets me isolate functionality and test it independently. So, I might end up with something like:
# controllers/foo_controller.rb
class FooController < ApplicationController
before_action :authenticate
def create
#results = FooManager.create(params)
redirect_to (#results[:success] ? my_happy_path : my_sad_path)
end
def authenticate
redirect_to unauthorized_path unless AuthenticationManager.authenticate(params, request)
end
end
# managers/foo_manager.rb
class FooManager
class << self
def create(params)
# do a bunch of great stuff and return a hash (perhaps a
# HashWithIndifferentAccess, if you like) which will
# allow for evaluation of #results[:success] back in the
# controller.
end
end
end
# managers/authentication_manager.rb
class AuthenticationManager
class << self
def authenticate(params, request)
# do a bunch of great stuff and return a boolean
end
end
end
With an approach like this, I can very easily test FooManager.create and AuthenticationManager.authenticate (as well as FooController.create and FooController.authenticate routing) all independently. Hooray!
Now, whether your authentication framework or controller method is behaving correctly at a unit level, as Dave points out very well, is a separate issue from whether your entire system is behaving as expected. I'm with him on having high-level integration tests so you're clear about what 'done' looks like and you know when to holler 'ship it!'
I have a rails application that I am implementing the Twilio SMS API on, and I am a bit lost on how to test drive my design.
To start I've just made a model that is an SMS mailer that will encapsulate the twilio API and I want to be able to test it and ensure functionality without using up SMS credits or bombarding someone with test text messages.
I know how to implement the API and get it working in the code but what I need help with is actually testing the code to make sure it works and prevent breakage in the future. Could anyone provide some advice?
Thanks!
You could use my gem Twilio.rb, which is already tested, and then mock it out in your tests, e.g. with mocha
Twilio::SMS.expects(:create).with :to => '+19175551234', :from => '+12125551234', :body => 'this is easy!'
Your unit tests should never hit external services, they should always be mocked. This is follows from a general principle of unit testing that tests should not extend the class boundary of the object being tested and collaborator objects should be mocked/stubbed.
Hope this helps!
https://github.com/stevegraham/twilio-rb
My experience with testing, and with testing Twilio applications, is that you test to eliminate risk you add. You'll want to use the Twilio gem rather than rolling your own SMS code against their REST endpoint: this minimizes the amount of risk.
Wrap the API as thinly as possible in your business logic class, and test primarily the business logic. For example, in my system, SMSes get sent out of the Reminder class. The code looks something like this:
class SomeWrapperClass
if (RAILS_ENV == "testing")
##sent_smses = []
cattr_accessor :sent_smses
end
def send_a_message(to, from, message, callback_url = nil)
unless RAILS_ENV == "testing"
Twilio::SMS.message(to, from, message, callback_url)
else
##sent_smses << {:to => to, :from => from, :message => message, :callback_url => callback_url}
end
end
end
This lets me write tests focusing on my business logic, which is the stuff I'm going to screw up. For example, if I want to test some method send_reminder(client) which sends a SMS message:
test "sends reminder to client" do
SomeWrapperClass.sent_smses = []
client = clients(:send_reminder_test_case)
Reminder.send_reminder(client)
sent_message = SomeWrapperClass.sent_smses.last
assert !sent_message.blank?, "Sending a reminder should fire an SMS to client."
assert sent_message.index(client.name) >= 0, "Sending a reminder should fire an SMS with the client's name in it.
...
end
Now I'm testing the actual risk I've added, which is that I'm screwing up Reminder.send_reminder. The wrapper, on the other hand, should be close to risk-free.
Obviously separate as much of the logic as possible. By doing this you can test everything else around as much as possible and then only leave the calls to the external API needing tests.
Working with external API's can be tricky. One option is to mock the response to something that you know will work for you or to the response you would expect, this can obviously be a bit brittle though. Another option is to look at something like VCR. This will record the call to the external API once and play it back again whenever you call it again.
This guy seems to have started solving your problem: https://github.com/arfrank/Fake-Twilio-Api
You probably don't need to test twiliolib's code, but if you don't want to stub twiliolib's methods you could use the FakeWeb gem, where you define the response for specified requests.
Similar to Steve mentioned, I just stub out the request with mocha:
# In Twilio initializer
TWILIO_ACCOUNT = Twilio::RestAccount.new(TWILIO_CONFIG[:sid], TWILIO_CONFIG[:token])
# In a test helper file somewhere
class ActiveSupport::TestCase
# Call this whenever you need to test twilio requests
def stub_twilio_requests
# Stub the actual request to Twilio
TWILIO_ACCOUNT.stubs(:request).returns(Net::HTTPSuccess.new(nil, nil, nil).tap { |n|
n.stubs(:body).returns("<?xml version=\"1.0\"?>\n<TwilioResponse></TwilioResponse>\n")
})
end
end
I use RoR 3 and i guess something changed in controller's tests.
There is no
def test_should_create_post
but
test "should create user" do
...
end
Is there any decription how is that mapping etc? Because i dont get it.
And second thing. How to program (what assertion) use to test login?
so the test "something here" style is rails way of helping us out. It is fundamentally the same as def test_as_you_want but they helped us out by taking away those nasty '_(underscores)' and wrapping the actual test wording in a string. This change came back, phew... maybe 2.3.x. that fact has to be checked but at least a year and a half ago.
Your second thing is a little more harder to answer man. What plugin are you using, or are you one of those guys who are writing their own auth system?
Either way, check out how the 'famous' auth plugins do it. from Restful Auth to Devise, basically you want test that you can:
Signup for the User account
all of your confirmation emails are sent etc..
Most of these 'cheat' or take the easy way out by passing a helper called signed_in users(:one) for instance. Assuming you are cool and using fixtures.
Basically here is what a helper method looks like if your Auth plugin/gem doesn't have one, like Clearance which didn't have it when i was first writing my tests... not sure if it has it now but it sheds light on how it should look. Notice I've commented out Restful Auth and how he/they did it:
#login user
def login_user(user = users(:one))
#Restful Auth Example
# #request.session[:user_id] = user ? users(user).id : nil
# Clearance
#controller.class_eval { attr_accessor :current_user }
#controller.current_user = user
return user
end
Actually i think i stole this from their shoulda login helper... that's probably what i did. Either way it shows you how to fake login a user.
Now when you are testing, just pass this login_user method to your test when you need a user logged in and start testing the rest of the method without worrying about them actually signing in. That is what the plugin is supposed to do and the 1000 people following it on github would scream if it didn't at least LOG that guy in.
cheers