I've been going nuts trying to write an automated test for my user sign up page. Users will be charged a recurring subscription via Stripe. They input their basic details (email, password, etc) and their credit card details on the same form, then the following flow happens:
(On the client-side) stripe.js makes an AJAX request to Stripe's servers, which (assuming everything is valid) returns a credit card token.
My javascript fills in a hidden input in the HTML form with the credit card token, and submits the form to my Rails server.
(Now on the server-side): I validate the user's basic details. If they're invalid, return (because there's no point charging them via Stripe if e.g. their email address is invalid so they can't create an account anyway.)
If they're valid, attempt to create a Stripe::Customer object, add the right subscription and charge them using Stripe's ruby gem etc.
All of this works perfectly fine... except I can't figure out how to test it. Testing step #4 is easy enough as it takes place on the server-side so I can mock out the Stripe calls with a gem like VCR.
Step #1 is what's giving me trouble. I've tried to test this using both puffing-billy and the stripe-ruby-mock gem, but nothing works. Here's my own javascript (simplified):
var stripeResponseHandler = function (status, response) {
console.log("response handler called");
if (response.error) {
// show the errors on the form
} else {
// insert the token into the form so it gets submitted to the server
$("#credit_card_token").val(response.id);
// Now submit the form.
$form.get(0).submit();
}
}
$form.submit(function (event) {
// Disable the submit button to prevent repeated clicks
$submitBtn.prop("disabled", true);
event.preventDefault();
console.log("creating token...");
Stripe.createToken(
// Get the credit card details from the form
// and input them here.
}, stripeResponseHandler);
// Prevent the form from submitting the normal way.
return false;
});
Just to reiterate, this all works fine when I test it manually. But my automated tests fail:
Failure/Error: expect{submit_form}.to change{User.count}.by(1)
expected result to have changed by 1, but was changed by 0
When I try to use the gem puffing-billy, it seems to be caching stripe.js itself (which is loaded from Stripe's own servers at js.stripe.com, not served from my own app, as Stripe don't support this.), but the call initiated by Stripe.createToken isn't being cached. In fact, when I log into my Stripe server logs, it doesn't seem that the call is even been made (or at least Stripe isn't receiving it.)
Note those console.log statements in my JS above. When I run my test suite, the line "creating token..." gets printed, but "response handler called." doesn't. Looks like the response handler is never being called.
I've left out some details because this question is already very long, but can add more on request. What am I doing wrong here? How can I test my sign up page?
UPDATE See [my comment on this Github issue] on stripe-ruby-mock for more info on what I've tried and failed.
If I understand correctly...
Capybara won't know about your ajax requests. You should be able to stub out AJAX requests with Sinatra. Have it return a fixtures much the same as VCR.
Here's an article on it.
https://robots.thoughtbot.com/using-capybara-to-test-javascript-that-makes-http
You need to boot the Sinatra app in Capybara and then match the URLs in your ajax calls.
Something like:
class FakeContinousIntegration < Sinatra::Base
def self.boot
instance = new
Capybara::Server.new(instance).tap { |server| server.boot }
end
get '/some/ajax'
# send ajax back to capybara
end
end
When you boot the server, it will return the address and port which you can write to a config that your js can use.
#server = App.boot
Then I use the address and port to config the JS app
def write_js_config
config['api'] = "http://#{#server.host}:#{#server.port}"
config.to_json
end
In spec_helper.rb send in the config to the js so your script points to your sinatra app. Mine compiles with gulp. So I just build the config into to is before the tests run:
system('gulp build --env capybara')
I've had tests which worked on manual fail in Capybara/poltergeist due to timeout. In my case, the solution was to wait for all AJAX requests to finish. Reference
Not sure whether Stripe.js uses JQuery internally, try checking for a condition set by stripeResponseHandler.
In addition to the wait_for_ajax trick mentioned, it looks like you are calling expect before your database was updated. One way to check that would be to add a breakpoint in your code(binding.pry), and check if it is a race condition issue or not.
Also, as per Capybara's documentation, introducing an expectation of a UI change makes it 'smartly' wait for ajax calls to finish:
expect(page).not_to have_content('Enter credit card details')
Related
I am trying to setup unit tests for all of my HTTP requests. Every request requires authentication, and with my app it requires authentication via cookie & DB query.
I have a preDispatch method in a parent controller that looks like this:
$this->cookie = Cookie::readCookie();
if (is_null($this->cookie))
{
return $this->failResponseView();
}
$this->dm = $this->getServiceLocator()->get('doctrine.documentmanager.odm_default');
//Does not have authoriziation
if (!$this->hasAppAccess())
{
return $this->failResponseView();
}
This has been working fine as far as the app is concerned. But running phpunit fails everytime because the cookie can't be read, or the response is being written before it is read.
This is me mirroring what I do in the regular app, in my test setup method:
$this->_cookie = new Cookie(array('access_token' => $profile['token']));
$this->_cookie->setCookie();
However, I receive this Exception when the code reaches this point. My question is, how can I fake, or bypass my cookie authentication when running phpunit to make sure all of these authenticated requests work?
Cannot modify header information - headers already sent by (output started at D:
\www\app\vendor\phpunit\phpunit\PHPUnit\Util\Printer.php:172)
UPDATE
It looks like since the PHPUnit\Util\Printer is outputting to STDOUT (see above), it's not liking that I am trying to write a cookie. Running this allowed full execution
phpunit --stderr
So I am able to call the setCookie() method, and it executes fine. But when I get to the point where it does Cookie::readCookie(), even though it's already been set, it can't read it. It returns null.
So question is still pretty much the same. What do I do to test this app if it uses cookie authentication?
Ugh, it's always something simple. In my setup method, I can just do this...
$_COOKIE[$name]= $this->_cookie->getData();
How can I send an asynchronous email with rails 4? By that I mean, when I click the 'send' button of a contact form, the page doesn't refresh, but the email is validated and sent, and a message is flashed to the user.
I've configured action_mailer correctly, and have a ContactForm mailer with one contact action that takes an email address as a parameter.
As a result,
ContactForm.contact("test#gmail.com").delivers #=> delivers email perfectly
But that's working on the command line. I don't really know the correct way to do this with a link. I mean, I could create a button that naviagates to send_email, and then I could have a route like this:
get 'send_email', to: 'contact#sendemail'
Then I would have a sendemail action which contains this method chain as shown above.
But this isn't asynchronous, and, also, I have no idea how I could validate the email's fields before sending the email, or highlighting invalid fields.
Is Ajax and JSON responses the key to highlighting the fields? What about the validation?
The resque_mailer seems to be a good way to send asyncronous emails. But why do I need this external gem when ajax is handled so well by vanilla rails?
The concept would be to have the form submit remotely. i.e submit to a create method in ContactsController. The method would then call a worker (resque/sidekiq) to send the email.
The create action can also respond to json. The json, response can either be a success or a fail (with errors).
On the AJAX success callback, you can trigger an alert, display div, or whatever notifying the user that the email was sent.
If the json results are returned with erros, then you can display the error message via JS.
This Railscasts Episode #171 Demonstrates sending emails using a background process with the help of DelayedJob
In my Rails app, I used the following jQuery to disable the ability to submit a form by pressing 'enter' in an input field:
$('input').on('keydown', function(e) {
if(e.which == '13') {
return false;
}
})
I wanted to test this with Capybara, but can't figure out how. For example:
# the form being tested creates a new Shift object:
test "enter does not submit form" do
shift_count = Shift.count
# fill in form, etc...
page.driver.browser.execute_script "var e = jQuery.Event('keydown'); e.which = 13; $('#shift_employee').trigger( e );"
assert Shift.count == shift_count
end
This test passes even when the jQuery function is commented out, presumably because it calculates Shift.count before ActiveRecord has had time to save the new the record to the database.
I also tried having the jQuery function do a custom $.get() request, to which the controller responds with simple text, thinking that this would build in enough time for all server-side work to be completed. But this still doesn't work, and I think it's because, no matter how I set up the jQuery function, the submission of the form and saving of the new record will always occur after any AJAX stuff I build in.
Any ideas on how I can reliably test that the form was not submitted?
So the form submission is done via AJAX? This is a common problem that testers run in to with Capybara. What you need to do is wait for the AJAX to complete.
There are two ways to do this. One is very specific and is the preferred method, the other is generic and should only be used if the first method can't be done.
The first way is to wait for content to change on the DOM. In your case nothing might change, but if, for example, you had a warning come up saying "You cannot press Enter, you must click the Save button!" then you could wait for that to come up by doing something like:
page.driver.browser ... # your code
page.should have_css(".info", :text => "You cannot ...etc...")
Of course you don't have to use the :text option though.
The other way is to make a helper method, like wait_for_ajax, that does a generic wait until AJAX is complete.
def wait_for_ajax
start = Time.now
while true
break if (page.evaluate_script('$.active') == 0)
if Time.now > start + Capybara.default_wait_time.seconds
fail "AJAX did not register as complete after #{Capybara.default_wait_time} seconds!"
end
sleep 0.1
end
end
This is based off the old way that people used to check for AJAX, but this new script is preferred for Capybara 2.0.
Then your step would do:
page.driver.etc
wait_for_ajax
That wait_for_ajax method can go in to any file Cucumber will load.
I got it to work by having Capybara revisit the page, and then checking Shift.count. The test now fails when the jQuery function is commented out, and passes when the function is uncommented. I'd still like to know if there's a better way, though.
Hi I am trying to test google auth with cucumber using vcr with a tag.
Everything goes fine till token expires. I think when it expires this happens
But I have a file with this content
http_interactions:
- request:
method: post
uri: https://accounts.google.com/o/oauth2/token
body:
If I allow vcr to record new requests the content of this cassette changes. I don't understand why, if the method and uri do not change POST to https://accounts.google.com/o/oauth2/token.
I changed tag to record new episodes and now test is passing... I am clueless.
I run the test again and now I am having this when POST to token url is being done:
Completed 500 Internal Server Error in 449ms
Psych::BadAlias (Unknown alias: 70317249293120):
Maybe you have some parameters inside the post, which are different for every request? If so, you can tell VCR to ignore this parameters by adding match_requests_on: [:method, VCR.request_matchers.uri_without_params("your_param")] to your VCR configuration.
In depth analyse your request, and find out which parameters are changing. You can tell VCR also to match on other criterias, have a look here https://www.relishapp.com/vcr/vcr/v/2-4-0/docs/request-matching
Ok, here's a solution...
The problem comes, as I said in the comment, from refreshing the token. When using oauth you have a token, which may be expired (or not). If you run the test and the token is fresh, that request isn't called. But if the token has expired it has to refresh it, and thus vcr throws an error.
To solve that, what I did is add the refresh token url to the ignored requests of vcr:
VCR.configure do |c|
c.cassette_library_dir = 'fixtures/vcr_cassettes'
c.hook_into :webmock # or :fakeweb
c.ignore_request {|request| request.uri == 'https://accounts.google.com/o/oauth2/token' }
end
It's not the best solution, since sometimes the token gets refreshed in the tests... but it's the best solution I could find...
I was getting the same issue with the same URL. For me, the problem was that my code was attempting to make the same call to https://accounts.google.com/o/oauth2/token more than once.
One of the potential solutions given in the VCR error message tells you the solution:
The cassette contains an HTTP interaction that matches this request, but it has already been played back. If you wish to allow a single HTTP interaction to be played back multiple times, set the :allow_playback_repeats cassette option
In my case, adding this option fixed the problem, as it tells VCR to revert back to its 1.x functionality of not re-recording duplicate requests, but simply playing back the result of a previously recorded duplicate request.
I am using Cucumber, so my solution was to add the following to my features/support/vcr.rb:
VCR.cucumber_tags do |t|
t.tag '#vcr', use_scenario_name: true
t.tag '#new_episodes', record: :new_episodes
t.tag '#allow_playback_repeats', use_scenario_name: true, allow_playback_repeats: true, record: :new_episodes
end
Notice the #allow_playback_repeats tag. I simply tagged my scenario with this tag, and everything worked properly thereafter:
#allow_playback_repeats
Scenario: Uploading a video initiates an upload to YouTube
Note that it doesn't work if you specify both #vcr and #allow_playback_repeats.
If you're using RSpec, you'll need to adapt the solution accordingly, but, it should be as simple as:
it "does something", :vcr => { allow_playback_repeats: true } do
...
end
I met the same problem, and finally found the there is a parameter changed every time.
you my solution is: copy and paste the mock parameter and real parameter together, and compare with them , and also make sure your next unit test would generate new parameter.
I'm writing a spec to verify that my Video model will create a proper thumbnail for a vimeo video when it is created. It looks something like this:
it "creates thumbnail url" do
vimeo_url = "http://player.vimeo.com/video/12345"
vid = Factory.build(:video, video_url:vimeo_url)
# thumbnail created when saved
vid.save!
expect do
URI.parse(vid.thumbnail_url)
end.to_not raise_error
end
The problem is that my test is super slow because it has to hit vimeo.com. So I'm trying to stub the method that calls to the server. So two questions:
1) Is this the correct way/time to stub something
2) If yes, how do I stub it? In my Video model I have a method called get_vimeo_thumbnail() that hits vimeo.com. I want to stub that method. But if in my spec I do vid.stub(:get_vimeo_thumbnail).and_return("http://someurl.com") it doesn't work. When I run the test it still hits vimeo.com.
The VCR gem is probably worth considering. It hits the real Web service first time you run it and records the response so that it can be replayed next time you run the test (making subsequent tests fast).
I can't see anything wrong with the stub call you are making if you are calling stub before save!.
I also second the use of the 'vcr' gem.
There's also a (pro)-episode of Railscast available about VCR:
http://railscasts.com/episodes/291-testing-with-vcr
VCR can be used to record all outgoing webservice calls into "cassettes" (fixtures) that will be replayed when the tests are run again. So you get the initial set of "real-world" responses but will not hit the remote api anymore.
It also has options to do "on demand" requests when there is no recorded response available locally, and also to make explicit "live" requests.
You can, and should, run tests agains the live endpoint from time to time to verify.