Mocking HTTP requests in ember-qunit - ember.js

In an ember-cli app, testing is done using ember-qunit.
I would like to mock HTTP requests, but have not been able to find a recommended way of doing this in the documentation.
I have found this thread which discusses this, but it appears to be out of date (for ember-cli anyway).
How do you mock HTTP requests?

This is how I mock HTTP requests. One improvement could be done by encapsulating mockjax with helper like:
function stubEndpointForHttpRequest(url, json) {
$.mockjax({
url: url,
dataType: 'json',
responseText: json
});
}
So you can easily switch for another library like sinon or whatever.
module('Integration - Signin Tests', {
setup: function(){
App = startApp();
},
teardown: function(){
Ember.run(App, 'destroy');
$.mockjaxClear(); // Don't forget to clear mockjax
}
});
test('Signin with valid data', function(){
expect(2);
stubEndpointForHttpRequest('api_url', 'response_json');
// Write your test
});
I Hope this helps

Related

Injection of service into Ember tests

I have read and followed EmberJS Service Injection for Unit Tests (Ember QUnit) but I'm still not able to figure where the problem is.
I would like to test if my authentication is working as expected. I have written authenticator for ember-simple-auth and session is injected into route. Code itself is working without any issues.
export default Ember.Route.extend({
authManager: Ember.inject.service('session'),
...
(in actions):
this.get('authManager').invalidate()
Now, I want to create a test which will test if my authentication is working as I expect. So I wish to use authManager directly.
moduleFor('route:index', 'Unit | Route | xyz', {
needs: ['service:session']
});
test('2', function(assert) {
let route = this.subject();
let s = route.get('authManager');
When I print the content of 's', I get ''. If I change this to something else, then response is undefined as can be expected. Problem is when I want to obtain property 'isAuthenticated' or run 'invalidate()'. In these cases I got 'undefined'. What am I doing wrong?
As of Ember 2.13, the correct solution to this is to use this.register:
test('my test', function(assert) {
this.register('service:session', Ember.Service.extend({
/* mock code */
}));
let subject = this.subject();
// test code goes here...
}
In a unit test, we prefer to use mock objects instead of services. In integration tests, we may use real services instead of mocks.
To mock a service, in a unit test:
var stubMyService = Ember.Object.extend({
//This is a mock object, write a code to test component/route!
invalidate: function() {
return 'invalidateCalled';
},
isAuthenticated: function(){
return true;
}
});
To inject this mock object to your component/route use this.subject() creation as following:
test('2', function(assert){
var component = this.subject({
authManager: stubMyService.create()
});
...
});

Custom post-request with RESTAdapter

My models work with the server via Ember's default RESTAdapter.
I just created a custom endpoint /mail on my server which sends an e-mail if provided a name, valid e-mail-adress and text.
How do I make Ember send that custom post-request? Is it possible without Ember.ajax at all?
For me personally, I wouldn't use Ember-Data to handle that scenario; I generally only use Ember-Data to handle my persisted models. If you try to use Ember-Data for other AJAX calls, it's just going to become a mess. Remember that Ember-Data's job is to manage your persisted data and one way that it can do that is with AJAX calls. That doesn't mean that anything that requires an AJAX call should be handled with Ember-Data.
I have this same issue and I wrote a utility module that has functions for all of my non-model AJAX stuff. This makes it really easy to swap out for testing. Here's a small example:
// utils/ajax.js
export function sendHelpEmail(comment) {
return new Promise((resolve, reject) => {
$.ajax({
type: 'POST',
url: '/api/contact_us',
contentType: 'application/json',
data: JSON.stringify({ comment }),
processData: false,
statusCode: {
200: () => Em.run(null, resolve),
500: () => Em.run(null, reject)
}
});
});
}
Then, I can do something like this in my controller:
import { sendHelpEmail} from '../utils/ajax.js';
export default Em.Controller.extend({
actions: {
sendEmail() {
sendHelpEmail(this.get('comment'));
}
}
});

How to mock an Ember-CLI service in an acceptance test?

Quick summary/tldr:
It seems that Ember's container lookup process + Ember-CLI's module resolver doesn't allow manually un-registering a service and then registering a replacement if the original service can be resolved using the resolver (I want to do the method described here, but it doesn't work)
How can I mock an Ember-CLI service in an acceptance test without using a hacky, custom resolver? (example project/acceptance test here)
Detailed explanation + example
Create a new service that is injected into a controller:
ember generate service logger
services/logger.js
export default Ember.Object.extend({
log: function(message){
console.log(message);
}
});
initializers/logger-service.js
export function initialize(container, application) {
application.inject('route', 'loggerService', 'service:logger');
application.inject('controller', 'loggerService', 'service:logger');
}
The service is accessed through its injected name, loggerService, in an action handler on the application controller:
Use the service in a controller
templates/application.hbs
<button id='do-something-button' {{action 'doSomething'}}>Do Something</button>
controllers/application.hs
export default Ember.Controller.extend({
actions: {
doSomething: function(){
// access the injected service
this.loggerService.log('log something');
}
}
});
Attempt to test that this behavior occurs correctly
I created an acceptance test that checks that the button click triggered the service. The intent is to mock out the service and determine if it was called without actually triggering the service's implementation -- this avoids the side-effects of the real service.
ember generate acceptance-test application
tests/acceptance/application-test.js
import Ember from 'ember';
import startApp from '../helpers/start-app';
var application;
var mockLoggerLogCalled;
module('Acceptance: Application', {
setup: function() {
application = startApp();
mockLoggerLogCalled = 0;
var mockLogger = Ember.Object.create({
log: function(m){
mockLoggerLogCalled = mockLoggerLogCalled + 1;
}
});
application.__container__.unregister('service:logger');
application.register('service:logger', mockLogger, {instantiate: false});
},
teardown: function() {
Ember.run(application, 'destroy');
}
});
test('application', function() {
visit('/');
click('#do-something-button');
andThen(function() {
equal(mockLoggerLogCalled, 1, 'log called once');
});
});
This is based on the talk Testing Ember Apps: Managing Dependency by mixonic that recommends unregistering the existing service, then re-registering a mocked version:
application.__container__.unregister('service:logger');
application.register('service:logger', mockLogger, {instantiate: false});
Unfortunately, this does not work with Ember-CLI. The culprit is this line in Ember's container:
function resolve(container, normalizedName) {
// ...
var resolved = container.resolver(normalizedName) || container.registry[normalizedName];
// ...
}
which is part of the container's lookup chain. The issue is that the container's resolve method checks the resolver before checking its internal registry. The application.register command registers our mocked service with the container's registry, but when resolve is called the container checks with the resolver before it queries the registry. Ember-CLI uses a custom resolver to match lookups to modules, which means that it will always resolve the original module and not use the newly registered mock service. The workaround for this looks horrible and involves modifying the resolver to never find the original service's module, which allows the container to use the manually registered mock service.
Modify Resolver to avoid resolving to original service
Using a custom resolver in the test allows the service to be successfully mocked. This works by allowing the resolver to perform normal lookups, but when our service's name is looked up the modified resolver acts like it has no module matching that name. This causes the resolve method to find the manually registered mock service in the container.
var MockResolver = Resolver.extend({
resolveOther: function(parsedName) {
if (parsedName.fullName === "service:logger") {
return undefined;
} else {
return this._super(parsedName);
}
}
});
application = startApp({
Resolver: MockResolver
});
This seems like it shouldn't be necessary and doesn't match the suggested service mocking from the above slides. Is there a better way to mock this service?
The ember-cli project used in this question be found in this example project on github.
Short version of the solution: your registered mock service must have a different service:name than the "real" service you're trying to mock.
Acceptance test:
import Ember from 'ember';
import { module, test } from 'qunit';
import startApp from 'container-doubling/tests/helpers/start-app';
var application;
let speakerMock = Ember.Service.extend({
speak: function() {
console.log("Acceptance Mock!");
}
});
module('Acceptance | acceptance demo', {
beforeEach: function() {
application = startApp();
// the key here is that the registered service:name IS NOT the same as the real service you're trying to mock
// if you inject it as the same service:name, then the real one will take precedence and be loaded
application.register('service:mockSpeaker', speakerMock);
// this should look like your non-test injection, but with the service:name being that of the mock.
// this will make speakerService use your mock
application.inject('component', 'speakerService', 'service:mockSpeaker');
},
afterEach: function() {
Ember.run(application, 'destroy');
}
});
test('visit a route that will trigger usage of the mock service' , function(assert) {
visit('/');
andThen(function() {
assert.equal(currentURL(), '/');
});
});
Integration test (this is what I was originally working on that caused me issues)
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import Ember from 'ember';
let speakerMock = Ember.Service.extend({
speak: function() {
console.log("Mock one!");
}
});
moduleForComponent('component-one', 'Integration | Component | component one', {
integration: true,
beforeEach: function() {
// ember 1.13
this.container.register('service:mockspeaker', speakerMock);
this.container.injection('component', 'speakerService', 'service:mockspeaker');
// ember 2.1
//this.container.registry.register('service:mockspeaker', speakerMock);
//this.container.registry.injection('component', 'speakerService', 'service:mockspeaker');
}
});
test('it renders', function(assert) {
assert.expect(1);
this.render(hbs`{{component-one}}`);
assert.ok(true);
});
You can register your mock and inject it instead of the original service.
application.register('service:mockLogger', mockLogger, {
instantiate: false
});
application.inject('route', 'loggerService', 'service:mockLogger');
application.inject('controller', 'loggerService', 'service:mockLogger');
I use this approach for mocking the torii library in my third-party login acceptance tests. I hope there will be a nicer solution in the future.
The existing answers work well, but there's a way that avoids renaming the service and skips the inject.
See https://github.com/ember-weekend/ember-weekend/blob/fb4a02353fbb033daefd258bbc032daf070d17bf/tests/helpers/module-for-acceptance.js#L14 and usage at https://github.com/ember-weekend/ember-weekend/blob/fb4a02353fbb033daefd258bbc032daf070d17bf/tests/acceptance/keyboard-shortcuts-test.js#L13
I'll present it here as an update to the test helper I previously had here, so it's a drop-in replacement, but you may just want to follow the links above instead.
// tests/helpers/override-service.js
// Override a service with a mock/stub service.
// Based on https://github.com/ember-weekend/ember-weekend/blob/fb4a02353fbb033daefd258bbc032daf070d17bf/tests/helpers/module-for-acceptance.js#L14
// e.g. used at https://github.com/ember-weekend/ember-weekend/blob/fb4a02/tests/acceptance/keyboard-shortcuts-test.js#L13
//
// Parameters:
// - newService is the mock object / service stub that will be injected
// - serviceName is the object property being replaced,
// e.g. if you set 'redirector' on a controller you would access it with
// this.get('redirector')
function(app, newService, serviceName) {
const instance = app.__deprecatedInstance__;
const registry = instance.register ? instance : instance.registry;
return registry.register(`service:${serviceName}`, newService);
}
Plus performing the jslint and helper registration steps from https://guides.emberjs.com/v2.5.0/testing/acceptance/#toc_custom-test-helpers
I can then call it like this, in my example stubbing out a redirect (window.location) service, which we want to do because redirecting breaks Testem:
test("testing a redirect's path", function(assert) {
const assertRedirectPerformed = assert.async();
const redirectorMock = Ember.Service.extend({
redirectTo(href) {
assert.equal(href, '/neverwhere');
assertRedirectPerformed();
},
});
overrideService(redirectorMock, 'redirector');
visit('/foo');
click('#bar');
});

Can't resolve RSVP.Promise in QUnit integration test

I am writing an integration test for my Ember.js application in QUnit. Before a test, I want to seed some test data by issuing HTTP requests to a dedicated testing API. I use jQuery.post to issue POST requests and I use Ember.RSVP.Promise.cast to turn the jQuery promise into an RSVP promise. However, it never seems to resolve. In the code below, it just hangs. The string "STARTING" is printed but neither "DONE" nor "FAIL" is printed.
I also tried creating a new RSVP Promise as described in the "Advanced usage" section of http://emberjs.com/api/classes/Ember.RSVP.Promise.html, to no avail (it also hanged). If I don't wrap the jQuery promise into an RSVP Promise, it does reach either the "DONE" or "FAIL".
Why doesn't the RSVP Promise resolve?
function create_teacher() {
var url = "<%= testing_teacher_path %>";
return Ember.RSVP.Promise.cast(
Ember.$.post(
url,
{
user: {
first_name: "John",
last_name: "Doe"
school: "EE3",
email: "john#doe.com",
password: "password"
}
}
)
);
}
module("Teacher Dashboard", {
setup: function() {
console.log("STARTING");
Ember.run(HstryEd, HstryEd.advanceReadiness);
},
teardown: function() {
console.log("TEARING DOWN");
HstryEd.reset();
}
});
asyncTest("Login", function() {
expect(1);
var teacher = create_teacher();
teacher.then(function() {
console.log("DONE");
ok(true, "done");
start();
},
function() {
console.log("FAIL");
ok(false, "fail");
start();
});
});
It could have to do with the Ember runloop being disabled in test mode. Have you checked out ic-ajax? https://github.com/instructure/ic-ajax It gives you promise-style jQuery ajax requests in a form that Ember likes, even in testing. I brought it in to solve my Ember runloop issues in testing, and have had great results so far.
Alternatively, you could try wrapping your teacher.then(.. in an Ember.run.

Getting timeout error when testing ajax behavior within ember component with sinon and mocha

I am trying to test an ember component with mocha and sinon. I wanted to test one of the actions of the component which makes an ajax call by using sinon's "useFakeXMLHttpRequest". But this test is causing time-out error. I am using mocha test adapter for ember taken from https://github.com/teddyzeenny/ember-mocha-adapter, I couldn't find the js file in cloud so I have pasted in whole code - so it might look bit messy in the jsbin.
Here is a jsbin link to the issue : http://jsbin.com/usajOhE/1/
The code for the component is :
AS.QuestionViewComponent = Ember.Component.extend({
templateName: "components/question-view",
actions: {
makeAjaxCall: function() {
jQuery.ajax({
url: "/todo/items",
success: function(data) {
//callback(null, data);
}
});
}
}
});
The handle bar associated with the component is :
<a {{action "makeAjaxCall"}} class="test-link">Make ajax call</a>
And my test script is:
describe("Testing", function() {
var xhr, requests;
before(function() {
xhr = sinon.useFakeXMLHttpRequest();
requests = [];
xhr.onCreate = function(req) {
requests.push(req);
};
});
after(function() {
xhr.restore();
});
beforeEach(function() {
AS.reset();
visit("/");
});
it("shoud make ajax call", function() {
//TIMESOUT HERE
click($("a.test-link:first")).then(function() {
console.log(requests);
expect(requests.length).to.be(1);
});
});
});
Your help will be much appreciated. Thanks
Most likely it is because you have not responded to the fake ajax request. The ember-testing package counts the pending ajax requests made by jQuery (see pendingAjaxRequests here). If this stays at 1, the ember-testing wait() helper never resolves.
The ember-testing package increments this counter via ajaxStart and ajaxStop filters.
To clarify what's happening here: When you use the click() helper, this sends a click message to the element, and then defers to the wait() helper (a promise). The same applies for other helpers such as fillIn(), keyEvent() etc. You can see from the comments in the source for wait() that it will not progress on with the rest of your specs:
// 1. If the router is loading
// 2. *If there are pending Ajax requests
// 3. If there are scheduled timers or we are inside of a run loop
The fix:
Unfortunately, if you never make it to the then block of your test, you cannot fake a response via requests[0].respond(...).
Instead, I've solved this by using sinon's fake server:
var server;
beforeEach(function () {
server = sinon.fakeServer.create();
server.autoRespond = true;
server.autoRespondAfter = 1; // ms
App.reset();
});
afterEach(function () {
server.restore();
});
it("should make ajax call", function() {
// set up the fake response
server.responses[0].response = [200, { "Content-Type": "application/json" }, '{ "todos": [] }'];
visit('/')
.click($("a.test-link:first"))
.then(function() {
// should make it to here now
});
});
This pattern works fine when you are expecting a single, or a deterministic order of ajax requests going into your fake server. If you expect lots of requests (with different paths), you can use server.respondWith([regex], ...) to match certain urls to specific responses.
Another thing to note is that it's generally good practice to put the success part of your ajax call into an Ember.run:
jQuery.ajax({
url: "/todo/items",
success: function(data) {
Ember.run(function () {
//callback(null, data);
})
}
});