I would like to test a factory method that runs $resource. My test would mock the real backend that does not exist yet.
Here is a sample factory code:
app.factory( 'Global', function( $resource ){
var Jogas = $resource('/jogasok/:id', {'id': '#id'}, {
'ujBerlet': {'method': 'POST', 'params': {'berlet': true}}
});
var jogasok = Jogas.query();
return {
getJogasok: function() {
return jogasok;
}
};
})
My test would be to check that query was made.
If I initialize my app with:
app.run( function run ($httpBackend) {
$httpBackend.whenGET('/jogasok').respond([
{id: 1, name: 'asdfasdf'},
{id: 2, name: '2wrerwert'}
]);
})
and open the app in my browser, then everything seems to be fine, I have the dummy data in the browser.
But, when I take out the run code above, and write a test, things just don't work.
describe( 'Global Service: ', function() {
beforeEach( module('bkJoga') );
beforeEach( inject(function($httpBackend) {
$httpBackend.whenGET('/jogasok').respond([
{id: 1, name: 'asdfasdf'},
{id: 2, name: '2wrerwert'}
]);
}));
it('getJogasok should return everyone', inject(function(Global) {
expect(JSON.stringify(Global.getJogasok())).toBe(JSON.stringify([
{id: 1, name: 'asdfasdf'},
{id: 2, name: '2wrerwert'}
]));
}));
});
fails.
Here is a working test for the factory as you posted it. I added a variable $httpBackend for the injected httpBackend. And a call to $httpBackend.flush(). fiddle-demo (read to the end for full description of fiddle content)
describe( 'Global Service: ', function() {
var Global, $httpBackend
// load the relevant application module, with the service to be tested
beforeEach( module('bkJoga') );
beforeEach( function() {
// inject the mock for the http backend
inject(function(_$httpBackend_) {
$httpBackend = _$httpBackend_;
});
// mock the response to a particular get request
$httpBackend.whenGET('/jogasok').respond([
{id: 1, name: 'asdfasdf'},
{id: 2, name: '2wrerwert'}
]);
// inject the service to be tested
inject(function(_Global_) {
Global = _Global_;
});
});
it('should exist', function() {
expect(!!Global).toBe(true);
});
it('getJogasok should return everyone', function() {
$httpBackend.flush(); // <------------ need to flush $httpBackend
expect(JSON.stringify(Global.getJogasok())).toBe(JSON.stringify([
{id: 1, name: 'asdfasdf'},
{id: 2, name: '2wrerwert'}
]));
});
});
In any event, I would rewrite the factory a bit differently, because, as it is currently written, it queries the database only upon instantiation var jogasok = Jogas.query();. Because services in angularjs are singletons, in your app you will have only the data as they are at the time of instantiation. Therefore, later modifications to the data will not be reflected in your factory.
Here is an example of the factory and its unit-test that reflects this idea.
the factory:
app.factory('GlobalBest', function ($resource) {
return $resource('/jogasok/:id', {
'id': '#id'
}, {
'ujBerlet': {
'method': 'POST',
'params': {
'berlet': true
}
}
});
});
the test:
describe('GlobalBest Service: ', function () {
var srv, $httpBackend;
beforeEach(module('bkJoga'));
beforeEach(function () {
// inject the mock for the http backend
inject(function (_$httpBackend_) {
$httpBackend = _$httpBackend_;
});
// inject the service to be tested
inject(function (_GlobalBest_) {
srv = _GlobalBest_;
});
});
it('should exist', function () {
expect( !! srv).toBe(true);
});
it('query() should return everyone', function () {
// mock the response to a particular get request
$httpBackend.whenGET('/jogasok').respond([{
id: 1,
name: 'asdfasdf'
}, {
id: 2,
name: '2wrerwert'
}]);
// send request to get everyone
var data = srv.query();
// flush the pending request
$httpBackend.flush();
expect(JSON.stringify(data)).toBe(JSON.stringify([{
id: 1,
name: 'asdfasdf'
}, {
id: 2,
name: '2wrerwert'
}]));
});
it('get({id: 1}) should return object with id=1', function () {
// mock the response to a particular get request
$httpBackend.whenGET('/jogasok/1').respond({
id: 1,
name: 'asdfasdf'
});
var datum = srv.get({
id: 1
});
$httpBackend.flush();
expect(JSON.stringify(datum)).toBe(JSON.stringify({
id: 1,
name: 'asdfasdf'
}));
});
});
I wrote a fiddle-demo with 3 versions of the service: your original service 'Global', a new version 'GlobalNew' that returns the query() method, and finally a version 'GlobalBest' that returns directly the $resource. Hope this helps.
try changing it from .toBe to .toEqual. Jasmine does object reference equality with toBe, and deep comparison with toEqual.
Until now the best answer I could get was to create another app, that inherits my app + uses the ngMockE2E service. This way it should be possible to mock out these requests.
note: unfortunately, I could not get it working yet with my testing environment
Use angular.mock.inject to create the mocked the instance of the service and then you need call flush() of the mock $httpBackend, which allows the test to explicitly flush pending requests and thus preserving the async api of the backend, while allowing the test to execute synchronously.
describe('Global Service: ', function () {
var srv;
beforeEach(module('bkJoga'));
beforeEach(function () {
angular.mock.inject(function ($injector) {
srv = $injector.get('Global');
});
});
beforeEach(inject(function ($httpBackend) {
$httpBackend.flush();
$httpBackend.whenGET('/jogasok').respond([{
id: 1,
name: 'asdfasdf'
}, {
id: 2,
name: '2wrerwert'
}]);
}));
it('getJogasok should return everyone', inject(function (Global) {
expect(JSON.stringify(Global.getJogasok())).toBe(JSON.stringify([{
id: 1,
name: 'asdfasdf'
}, {
id: 2,
name: '2wrerwert'
}]));
}));
});
Demo
A nice article on AngularJS testing that touches on backend mocks too: http://nathanleclaire.com/blog/2013/12/13/how-to-unit-test-controllers-in-angularjs-without-setting-your-hair-on-fire/
Related
I'm using Ember CLI Storybook to create a story of a component than internally relies upon services that communicate to the internet, to fetch and post information to the backend. The way I'm doing that is using ember-ajax.
I see how to mock an ember model from this section but wondering if there is a workaround for ember ajax service.
I like to use mswjs.io for mocking remote requests. It uses a service worker so you can still use your network log as if you still used your real API.
I have an example repo here showing how to set it up: https://github.com/NullVoxPopuli/ember-data-resources/
But I'll copy the code, in case I change something.
Now, in tests, you'd want something like this: https://github.com/NullVoxPopuli/ember-data-resources/blob/main/tests/unit/find-record-test.ts#L17
module('findRecord', function (hooks) {
setupMockData(hooks);
But since you're using storybook, you'd instead want the contents of that function. (And without the setup/teardown hooks unique to tests)
https://github.com/NullVoxPopuli/ember-data-resources/blob/main/tests/unit/-mock-data.ts#L22
import { rest, setupWorker } from 'msw';
let worker;
export async function setupMockData() {
if (!worker) {
worker = setupWorker();
await worker.start();
// artificial timeout "just in case" worker takes a bit to boot
await new Promise((resolve) => setTimeout(resolve, 1000));
worker.printHandlers();
}
let data = [
{ id: '1', type: 'blogs', attributes: { name: `name:1` } },
{ id: '2', type: 'blogs', attributes: { name: `name:2` } },
{ id: '3', type: 'blogs', attributes: { name: `name:3` } },
];
worker.use(
rest.get('/blogs', (req, res, ctx) => {
let id = req.url.searchParams.get('q[id]');
if (id) {
let record = data.find((datum) => datum.id === id);
return res(ctx.json({ data: record }));
}
return res(ctx.json({ data }));
}),
rest.get('/blogs/:id', (req, res, ctx) => {
let { id } = req.params;
let record = data.find((datum) => datum.id === id);
if (record) {
return res(ctx.json({ data: record }));
}
return res(
ctx.status(404),
ctx.json({ errors: [{ status: '404', detail: 'Blog not found' }] })
);
})
);
}
Docs for msw: https://mswjs.io/
Hello I have checked the behaviour in application and it works with same data from api as I'm providing in mocked api call Api.contracts.getContractDetails.mockImplementationOnce(() => ({ data })); However, the value of hasWatermark computed is false - while it should be true.
How can I debug this? Is it possible to check computed in tests? This is my test:
function createWrapper() {
const i18n = new VueI18n({
locale: "en",
missing: jest.fn(),
});
return mount(EmployeeContract, {
i18n,
localVue,
store,
mocks: { $route: { query: {}, params: { id: "123" } }, $buefy: { toast: { open: jest.fn() } } },
stubs: ["spinner", "router-link", "b-switch"],
});
it("should add watermark for preview once it has rejected status", async () => {
const data = singleContract;
Api.contracts.getContractDetails.mockImplementationOnce(() => ({ data }));
const wrapper = createWrapper();
await flushPromises();
expect(wrapper.vm.hasWatermark).toBeTruthy();
});
So, I'm trying to use the Twitter-style URL syntax, allowing a user to go to example.com/quaunaut to visit the user page of the user with the username 'quaunaut'. I was able to accomplish this via:
app/router.js
export default Router.map(function() {
this.route('users.show', { path: '/:user_username' });
});
app/routes/users/show.js
export default Ember.Route.extend({
model: function(params) {
return this.store.find('user', { username: params.user_username }).then(function(result) {
return result.get('firstObject');
});
},
serialize: function(model) {
return { user_username: model.get('username') };
}
});
Now, when live or run via ember s, this works fantastically. However, in tests, it seems for some reason to not resolve.
var application, server, USERS;
USERS = {
'example1': [{
id: 1,
username: 'example1'
}],
'example2': [{
id: 2,
username: 'example2'
}]
};
module('Acceptance: UsersShow', {
beforeEach: function() {
application = startApp();
server = new Pretender(function() {
this.get('/api/users', function(request) {
return [
201,
{ 'content-type': 'application/javascript' },
JSON.stringify(USERS[request.queryParams.username])
];
});
});
},
afterEach: function() {
Ember.run(application, 'destroy');
server.shutdown();
}
});
test('visiting users.show route', function(assert) {
visit('/example1');
andThen(function() {
assert.equal(currentPath(), 'users.show');
assert.equal(find('#username').text(), 'example1');
});
});
Which results in the following test results:
Acceptance: UsersShow: visiting users.show route
✘ failed
expected users.show
✘ failed
expected example1
So, any ideas why currentPath() isn't resolving? If you also have any recommendations for better means to implement what I'm looking to do here, I'm certainly open to it.
Your visit syntax isn't quite right, should be:
test('visiting users.show route', function(assert) {
visit('/example1').then(function() {
assert.equal(currentPath(), 'users.show');
assert.equal(find('#username').text(), 'example1');
});
});
Trying to understand how do jasmine tests work.
I've got a module and a controller:
var app = angular.module('planApp', []);
app.controller('PlanCtrl', function($scope, plansStorage){
var plans = $scope.plans = plansStorage.get();
$scope.formHidden = true;
$scope.togglePlanForm = function() {
this.formHidden = !this.formHidden;
};
$scope.newPlan = {title: '', description: ''} ;
$scope.$watch('plans', function() {
plansStorage.put(plans);
}, true);
$scope.addPlan = function() {
var newPlan = {
title: $scope.newPlan.title.trim(),
description: $scope.newPlan.description
};
if (!newPlan.title.length || !newPlan.description.length) {
return;
}
plans.push({
title: newPlan.title,
description: newPlan.description
});
$scope.newPlan = {title: '', description: ''};
$scope.formHidden = true;
};
});
plansStorage.get() is a method of a service that gets a json string from localstorage and returns an object.
When I run this test:
var storedPlans = [
{
title: 'Good plan',
description: 'Do something right'
},
{
title: 'Bad plan',
description: 'Do something wrong'
}
];
describe('plan controller', function () {
var ctrl,
scope,
service;
beforeEach(angular.mock.module('planApp'));
beforeEach(angular.mock.inject(function($rootScope, $controller, plansStorage) {
scope = $rootScope.$new();
service = plansStorage;
spyOn(plansStorage, 'get').andReturn(storedPlans);
ctrl = $controller('PlanCtrl', {
$scope: scope,
plansStorage: service
});
spyOn(scope, 'addPlan')
}));
it('should get 2 stored plans', function(){
expect(scope.plans).toBeUndefined;
expect(service.get).toHaveBeenCalled();
expect(scope.plans).toEqual([
{
title: 'Good plan',
description: 'Do something right'
},
{
title: 'Bad plan',
description: 'Do something wrong'
}
]);
});
it('should add a plan', function() {
scope.newPlan = {title: 'new', description: 'plan'};
expect(scope.newPlan).toEqual({title: 'new', description: 'plan'});
scope.addPlan();
expect(scope.addPlan).toHaveBeenCalled();
expect(scope.plans.length).toEqual(3);
});
});
first test passes ok, but second one fails. The length of the scope.plans expected to be 3, but it is 2. scope.plans didn't change after scope.addPlan() call.
If I understand that right, the $scope inside addPlan method is not the same as scope that I trying to test in second test.
The question is why? And how do I test the addPlan method?
the solution is just to add andCallThrough() method after spy:
spyOn(scope, 'addPlan').andCallThrough()
I have an ember.js app and I'm setting up a DS.Store like this (view actual code):
(function (app) {
'use strict';
...
var store = DS.Store.extend({
revision: 12
});
app.Store = store;
})(window.Balanced);
Now I have a qunit test and in that test I would like to swap the default RESTAdapter for a FixtureAdapter so that I can setup fixtures for my models. I figure I need to write something like this but I'm not 100% sure:
(function () {
'use strict';
var fixtureAdapter;
module('tests.store.module', {
setup: function () {
fixtureAdapter = DS.FixtureAdapter.extend({
});
Balanced.Store.reopen({
adapter: fixtureAdapter
});
// TODO: how does this work?
Balanced.Marketplace.FIXTURES = [
{id: 1, name: '1'},
{id: 2, name: 'poop'},
{id: 3, name: 'poop'}
];
},
teardown: function () {
// teardown code
}
}
);
test("Marketplace query", function () {
var marketplaces = Balanced.Marketplace.find();
// TODO: how do I test this?
});
})();
For my basic unit testing with jasmine I setup the store manually like so (using the local storage adapter to avoid xhr requests)
describe ("CodeCamp.SessionView Tests", function(){
var get = Ember.get, set = Ember.set, sut, controller, session, store;
beforeEach(function(){
store = DS.Store.create({
revision: 11,
adapter: DS.LSAdapter.create()
});
sut = CodeCamp.SessionView.create();
controller = CodeCamp.SessionController.create();
controller.set("store", store);
sut.set("controller", controller);
session = CodeCamp.Session.createRecord({ id: 1, name: "First", room: "A", ratings: [], speakers: [], tags: []});
});
afterEach(function() {
Ember.run(function() {
store.destroy();
controller.destroy();
sut.destroy();
session.destroy();
});
store = null;
controller = null;
sut = null;
session = null;
});
it ("will create rating when form is valid", function(){
sut.set('score', '1234');
sut.set('feedback', 'abcd');
sut.addRating(session);
var ratings = CodeCamp.Session.find(1).get('ratings');
var rating = ratings.objectAt(0);
expect(rating.get('score')).toEqual('1234');
expect(rating.get('feedback')).toEqual('abcd');
expect(rating.get('session').get('id')).toEqual(1);
});
});
The test above goes end-to-end for the following ember view
CodeCamp.SessionView = Ember.View.extend({
templateName: 'session',
addRating: function(event) {
if (this.formIsValid()) {
var rating = this.buildRatingFromInputs(event);
this.get('controller').addRating(rating);
this.resetForm();
}
},
buildRatingFromInputs: function(session) {
var score = this.get('score');
var feedback = this.get('feedback');
return CodeCamp.Rating.createRecord(
{ score: score,
feedback: feedback,
session: session
});
},
formIsValid: function() {
var score = this.get('score');
var feedback = this.get('feedback');
if (score === undefined || feedback === undefined || score.trim() === "" || feedback.trim() === "") {
return false;
}
return true;
},
resetForm: function() {
this.set('score', '');
this.set('feedback', '');
}
});
If you want to see this entire app in action (just a sample ember app with a few basic jasmine tests) it's on github
https://github.com/toranb/ember-code-camp/