Testing AngularJS controllers using expectPOST - ruby-on-rails

I've got a AngularJS/Rails app and I want to test my AngularJS controller is posting to the backend server when creating a new record. (I'm using jasmine for my tests)
Here is my attempted test
describe "create", ->
beforeEach(inject ( ($controller, $rootScope, $location, $state, $httpBackend) ->
#redirect = spyOn($location, 'path')
#httpBackend.whenGET('/assets/layouts/default.html.erb').respond(200)
#httpBackend.whenGET('/assets/letters/index.html.erb').respond(200)
#httpBackend.whenPOST('/api/letters').respond(200)
$controller("LettersController", { $scope: #scope, $location: #location })
))
it "sends a post to the backend", ->
#httpBackend.expectPOST('/api/letters', {"letter":{},"_utf8":"☃"}).respond(200)
#scope.create()
Here s the code which I'm testing:
$scope.create = ->
Letter.save(
{}
,
letter:
subject: $scope.letter.subject
body: $scope.letter.body
# success
, (response) ->
$location.path "/letters"
# failure
, (response) ->
)
The code in question works correctly and the test passes. The problem is if I comment my Letter.save code out (which makes the post through AngularJS resources) then my test still passes.
How can I get my test to work properly?
My full test application is here: https://github.com/map7/angularjs_rails_example2

You need to verify that there are no outstanding requests at the end of your tests:
$httpBackend.verifyNoOutstandingRequest();

You also need to verify that there are no outstanding expectations with $httpBackend.verifyNoOutstandingExpectation
This should prevent you from getting false positives I believe.
Also, I'm not sure that your expectPOST should return true because you don't seem to be sending "_utf8":"☃", however I haven't looked at the full source code so I could be missing something.
I would try to trim down the example so that your create method calls a route and you expect that route to be called and work from there. You might try removing the whenPOST and replace the expectPOST with #httpBackend.expectPOST('/api/letters').respond(200)

Related

Cant find information about acceptable response from my site to zapier app on test step (It's "Action" on my site )

I can't find information about acceptable response content or code status from my site to zapier app on test step.
I have my site on Laravel and Zapier app for this site. In my Zapier app I have an action: "Create New Project". I made my "create" according to the example. Everything works except the testing step. I tested with the following zap:
Trello -> "New card created" trigger. Test successful.
My app -> "Create new project". Test fails with We had trouble sending your test through. Could not handle special, non-standard characters. Please contact support.
Strangely, the project was created successfully. Therefore, I think the problem lies in the response from my site to zapier:
// creates/project.js
// My perform function:
perform: (z, bundle) => {
const promise = z.request({
url: `${process.env.BASE_URL}/api/folder`,
method: 'POST',
body: JSON.stringify({
title: bundle.inputData.title,
}),
headers: {
'content-type': 'application/json',
}
});
return promise.then((response) => JSON.parse(response.content));
}
//ZapierController.php
public function addFolder(Request $request)
{
// Made record to DB, and other, after this returned same data which in request
return response()->json(['title' => $request['title']]);
}
Expected result - successful test on "Test this step". Can anyone help me?
David here, from the Zapier Platform team.
I already answered your support ticket, but I figured I'd reply here in case anyone else has the same issue.
The root problem is made clear when you run zapier logs on the cli:
== Log
Unhandled error: CheckError: Invalid API Response:
- Got a non-object result, expected an object from create ("title")
What happened:
Executing creates.сFolder.operation.perform with bundle
Invalid API Response:
- Got a non-object result, expected an object from create ("title")
Your server can reply with any 2xx status code, but the output needs to be valid json. Something like {"title": "my title here"} would certainly work. Users find it more helpful to get info about the project they just created, so the name, id, etc would be even better.
As for why this surfaced as a character encoding issue, I have no clue. We plan on getting to the bottom of it though!

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"}
})

Stub Rails UJS/Ajax responses status to test returning message in Rspec/Capybara feature test

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.

Spray: Testing File Uploads with Specs2

I have an API built using Spray that handles file uploads.
I am trying to write a test for the upload functionality but I'm not getting anywhere fast. I'm nots sure how to structure the test to simulate a file upload.
I have the following test...
"Valid POST Requests should return success" in {
Post("/upload", HttpEntity(MediaTypes.`multipart/form-data`, """{"filename":"a.wav"}""")) ~>
sealRoute(uploadRoute) ~> check {
response.status should be equalTo OK
responseAs[String] === "..."
}
}
Running this produces the following error message...
Content-Type with a multipart media type must have a non-empty 'boundary' parameter' is not equal to ...
This seems like an error message similar to how to mock POST/Upload requests using apache bench where you have to specify a post file and the boundary to separate the form items.
I was hoping for something closer to how CURL works.
Either way, can anyone point me in the right direction as to how I correctly structure such a test?
Thanks
So I managed to get this working by cobbling together some code from a variety of posts I found - primarily posts relating to using spray-client to do file uploads.
Probably not the prettiest but works for me! :)
"Valid POST Requests should return success" in {
val file = new File("a.wav")
val httpEntity = HttpEntity(MediaTypes.`multipart/form-data`, HttpData(file)).asInstanceOf[HttpEntity.NonEmpty]
val formFile = FormFile("file", httpEntity)
val mfd = MultipartFormData(Seq(BodyPart(formFile, "file")))
Post("/upload", mfd) ~> sealRoute(uploadRoute) ~> check {
response.status should be equalTo OK
body.contentType.toString() === "application/json; charset=UTF-8"
responseAs[String] === "Success!"
}
}
I have the same issue, or question.
Try adding a boundary by doing:
Post("/upload", HttpEntity(MediaTypes.multipart/form-data.withBoundary("-somerandomboundary"), """{"filename":"a.wav"}""")) ~>
Although, you might face the next bump I face, which is an error saying it requires a start boundary.

how to fetch json data from a remote server in a backbone.js app

I'm following an app example from this repo
and i started to build a similar app in my local machine using rails intead php for API, so i build apart the rails app in a specific root directory then in another root the backbone.js/JQM app calling rails api.
so i create the model and the collection in backbone with the urlRoot with: http://localhost:3000/api/clubs.json, that correspond to the local server url for retrieve a list of clubs
then i have tried to see in the javascript console what happen with these commands:
clubs = new ClubsCollection()
Object {length = 0 ...etc..}
clubs.fetch()
GET http://localhost:3000/api/clubs.json 200 OK
and the response is empty...
but when i call the url http://localhost:3000/api/clubs.json it returns the json clubs list correctly.
can you help me to understand the best way to do this call?
fetch is asynchronous. you have to wait for it to finish loading the data, and check the response then. This is typically done through the reset event:
clubs = new ClubsCollection();
clubs.on("reset", function(){
alert(clubs.length + " item(s) found");
});
clubs.fetch();
I have solved the problem using 'rack-cors' gem with the a basic config into
config/application.rb:
config.middleware.use Rack::Cors do
allow do
origins 'localhost'
resource '%r{/api/\$+.json}',
:headers => ['Origin', 'Accept', 'Content-Type'],
:methods => [:get, :put, :delete]
end
end
If you look at the backbone API there is a success callback that you can provide with the fetch() call, e.g.
clubs.fetch({
success : function(model, err) { // do something }
});
Like the other answer, fetch is asynchronous so you can't expect the results to be back by setting a break point after the .fetch() call.

Resources