Stub Rails UJS/Ajax responses status to test returning message in Rspec/Capybara feature test - ruby-on-rails

I have AJAX calls initiated by Rails UJS that I would like to test. specifically, I have used Rails UJS ajax events to provide for cases of errors.
I would like to test them but I don't know how to tell rspec/capybara to "stub" and assume the error code
$("button").
on('ajax:error',function(event,xhr, status, error){
if(status == "timeout") {
var msg;
msg = Messenger().post({
message: "This is taking too long"
});
} else {
var msg;
msg = Messenger().post({
message: "it seems there is a bug. Please try again."
});
};
});
I would like to do something like the following:
describe "test returning error message", js: true do
it "should be successful" do
visit deal_page_path(deal)
first('a.button').click
stub(:xhr.status) = "timeout"
expect(page).to have_content('This is taking too long')
end
end
How to do this?
Note: the ajax requests are internal they don't go to third party API or services (such as facebook for ex).

When testing with Capybara (JS enabled drivers) it has no access to the request or response except through the changes it creates in the browser. You could build a test mode into your relevant controllers that could be turned on and off to allow it to output the errors you want, but the cleanest way to do this is probably to use a programmable proxy like puffing-billy which will allow you to selectively return whatever you'd like for any given request from the browser. One thing to realize is that this isn't testing that app correctly returns errors, it's just testing that your front-end handles errors the way you expect.

Related

Where to find the message templates from Exception when testing?

I am new to RoR and testing. There is a conundrum when it comes to testing. I don't know what error messages returned from the default Exceptions such as ActiveRecord::ActiveRecordError, so I can't simply test using
expect(response.body).to match()
Where to find those message templates? The official API website is kind of useless in this case.
If you're using RSPEC you can try something like this
get(:action) { post :my_test, params: { 'event': 'testing' } }
it { expect { action }.to raise_error ActiveRecord::RecordNotFound }
This site will help you with tests http://betterspecs.org

How to purposely delay an AJAX response while testing with Capybara?

I have a React component that mimics the "link preview" feature that most modern social media sites have. You type in a link and it fetches the image, title, etc...
I do this by having the React component make an AJAX call back to my server to fetch the URL preview data.
While it's fetching I show an intermediate "loading" state (i.e. some loading icon or spinning wheel)
The relevant React snippet looks like
this.setState({ isLoadingAttachment: true })
return $.ajax({
type: "GET",
url: some_url,
dataType: "json",
contentType: "application/json",
}).success(function(response){
// Succesful! Do Success stuff
component.setState({ isLoadingAttachment: false })
}).error(function(response) {
// Uh oh! Handle failure stuff
component.setState({ isLoadingAttachment: false })
});
Note how the isLoadingAttachment state variable is only valid for a brief second while the server is doing the fetching. Both the success and error scenarios immediately disable it.
I'd like to test some functionality during my "loading" state with my Capybara feature specs. I've mocked all the web calls and the data to be returned by the server, but it all happens so quickly that it passes through the "loading" state before I can even run any expect().. statement on it. I also purposely don't call wait_for_ajax so the page will go ahead without waiting for the ajax, but it's still too fast.
Lastly I also tried purposefully delaying the server call by 1.0 second, but that didn't work either. I assume because the whole thing is single threaded somehow?
# `foo` is an arbitrary method called during the server-side execution
allow_any_instance_of(MyController).
to receive(:foo) { sleep(1.0) }.and_call_original
Any thoughts on how I could do this?
Thanks!
Capybara starts up the app server in a different thread than the tests, however if you're using the default Capybara.server setting you may have issues with your app calling back to itself since it uses webrick by default. Instead you should specify Capybara.server = :puma. Beyond that, mocking responses is generally a bad idea in feature specs (which are generally meant to be end-to-end tests) since it means you're not actually testing your apps code the way it would run in production anymore. A better solution is to use something like puffing-billy - https://github.com/oesmith/puffing-billy - to mock web responses outside of your apps code which would allow you to do something like
proxy.stub('https://example.com/proc/').and_return(Proc.new { |params, headers, body|
sleep 2
{ :text => "Your results"}
})

Rspec/capybara - simulate switch from Online to offline within a test (webkit driver)

I have a ruby on Rails 4 app.
A user loads a page (called deal_page) then he clicks on a button which loads a Rails UJS modal message.
If he is online WHEN he clicks the button, I show a message using javascript offline status recognition (inside the Rails UJS ajax event).
on('ajax:error',function(event,xhr, status, error){
var isOnLine = navigator.onLine;
event.preventDefault();
if (isOnLine) {
//do stuff }
else {
// for disconnected users WHEN triggering call to server, invite to reconnect
var msg;
msg = Messenger().post({
message: "You lost internet,You need to be connected"
});
}
});
I want to test in rspec/capybara (I use webkit driver) that the right content ("You need to be connected. Please connect and try again") is displayed in that case in my feature test.
context "As signed-in visitor who is OFFLINE line when he clicks on the area triggering the modal" do
it "displays the right message " do
visit deal_page_path(deal)
# HOW TO ASSERT THAT THE USER LOSES HIS INTERNET CONNECTION
# USER GOES OFFLINE
first("a#button").click
within('ul.messenger') do
expect(page).to have_content('You lost internet,You need to be connected')
end
end
end
The complexity is that I can't disconnect him from the beginning of the test as he needs to be connected to load the page visit deal_page_path(deal) but then I want to simulate that he loses the internet connection.
How to achieve this ?
Assuming you're just checking window.naviagtor.onLine to determine whether or not it is on/offline (not depending on any events or anything) and because you're using capybara-webkit (won't work in selenium since FF won't let you overwrite navigator, not sure about poltergeist) you can just overwrite window.navigator with the result you want to get. You'll also need to generate an error response for your ajax request, which you can do with puffing-billy
page.execute_script "window.navigator = { onLine: false }"

Ember/Rails end-to-end testing error

I have an Ember CLI app with a Rails back-end API. I am trying to set up end-to-end testing by configuring the Ember app test suite to send requests to a copy of the Rails API. My tests are working, but I am getting the following strange error frequently:
{}
Expected: true
Result: false
at http://localhost:7357/assets/test-support.js:4519:13
at exports.default._emberTestingAdaptersAdapter.default.extend.exception (http://localhost:7357/assets/vendor.js:52144:7)
at onerrorDefault (http://localhost:7357/assets/vendor.js:42846:24)
at Object.exports.default.trigger (http://localhost:7357/assets/vendor.js:67064:11)
at Promise._onerror (http://localhost:7357/assets/vendor.js:68030:22)
at publishRejection (http://localhost:7357/assets/vendor.js:66337:15)
This seems to occur whenever a request is made to the server. An example test script which would recreate this is below. This is a simple test which checks that if a user clicks a 'login' button without entering any email/password information they are not logged in. The test passes, but additionally I get the above error before the test passes. I think this is something to do with connecting to the Rails server, but have no idea how to investigate or fix it - I'd be very grateful for any help.
Many thanks.
import Ember from 'ember';
import { module, test } from 'qunit';
import startApp from 'mercury-ember/tests/helpers/start-app';
module('Acceptance | login test', {
beforeEach: function() {
this.application = startApp();
},
afterEach: function() {
Ember.run(this.application, 'destroy');
}
});
test('Initial Login Test', function(assert)
{
visit('/');
andThen(function()
{
// Leaving identification and password fields blank
click(".btn.login-submit");
andThen(function()
{
equal(currentSession().get('user_email'), null, "User fails to login when identification and password fields left blank");
});
});
});
You can check in the Network panel of Chrome or Firefox developer tools that the request is being made. At least with ember-qunit you can do this by getting ember-cli to run the tests within the browser rather than with Phantom.js/command-line.
That would help you figure out if it's hitting the Rails server at all (the URL could be incorrect or using the wrong port number?)
You may also want to see if there is code that needs to be torn down. Remember that in a test environment the same browser instance is used so all objects need to be torn down; timeouts/intervals need to be stopped; events need to be unbound, etc.
We had that issue a few times where in production there is no error with a utility that sent AJAX requests every 30 seconds, but in testing it was a problem because it bound itself to the window (outside of the iframe) so it kept making requests even after the tests were torn down.

Looking for paradigm to use for generic error handling in Angular from a JSON response from Rails

I'm building an app which is architected as a Rails server app providing RESTful api's to the client. The Rails server uses RABL. The client is an Angular JS client performing standard $http calls (gets, puts, etc).
Occasionally my Rails server will produce an error (let's say validation error attached to the object) or even no error in which case I would want to display something to the user - either the errror e.g., "The record did not save because..." or "The record was updated successfully".
I'm trying to map out a pattern on both the Rails side and the Angular/client side to handle this.
As for Rails:
I can certainly pass back a node in each of my RABL files to contain error arrays
I can also return different RABL by checking in the controller before returning
Most suggest using http codes (which makes sense) as per here (although there doesn't seem to be a consistent usages of the codes for something like a validation error).
As for Angular:
I suppose I can write a response interceptor but not sure how that would fully get flushed out.
I guess I'm hoping that I don't have to reinvent the wheel here and someone can point me to a pattern that's currently used and suggested (and localized).
I went ahead and implemented what I thought needed to be done. Thanks for digger69 for some help with this.
On the Rails side, I went with using an http status code. As per here I agreed with using a 400 http status code for error validation.
In my controllers I now have something like the following:
def create
my_obj = MyObj.build_with_params(params)
if my_obj.save
respond_with(my_obj) # regular RABL response
else
respond_with_errors(my_obj.errors)
end
end
In my application_controller.rb I defined a common method respond_with_errors
# respond back to the client an http 400 status plus the errors array
def respond_with_errors(errors)
render :json => {:errors => errors}, :status => :bad_request
end
Note that the :bad_request symbol is already defined for Rails as per here
On the client side I needed to intercept http calls (not only for validation but for authentication failures too (and probably more). Here is an example of my code in Angular (thanks to this post for the help with that):
var interceptor = ['$rootScope', '$q', function (scope, $q) {
function success(response) {
return response;
}
function error(response) {
var status = response.status;
if (status == 401) { // unauthorized - redirect to login again
window.location = "/";
} else if (status == 400) { // validation error display errors
alert(JSON.stringify(response.data.errors)); // here really we need to format this but just showing as alert.
} else {
// otherwise reject other status codes
return $q.reject(response);
}
}
return function (promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);
I now can be consistent with my rails code and deal with success returns from http calls on the client. I'm sure I have some more to do, but I think this gives a localized solution.
Use an HTTP response interceptor. I am currently using that successfully in an application.
http://docs.angularjs.org/api/ng.$http
From the documentation:
$provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
return function(promise) {
return promise.then(function(response) {
// do something on success
}, function(response) {
// do something on error
if (canRecover(response)) {
return responseOrNewPromise
}
return $q.reject(response);
});
}
});
$httpProvider.responseInterceptors.push('myHttpInterceptor');
In my case I created a feedback service, which displays either success or error messages globally. An other option would be to broadcast the responses on the rootscope.

Resources