Angular 1.5.x/Jasmine - Expected spy to have been called but it was never called - jasmine

POST-EDIT: I've just solved the issue, though maybe someone has a better solution. I'll post my solution soon, but if someone has a correct solution I'll accept their answer.
I'm migrating my application from 1.4 to 1.5 and changing all my controllers and directives to components. I now have a test that once worked not working and I'd like some guidance.
I'm trying to spy on a service method and according to the unit test it's not calling. This is not the case as an API call is made when the application is run. Here is the message I receive:
This is my component file:
(function(){
"use strict";
angular.module("app").component("profileComponent", {
templateUrl: "/templates/profile.component.html",
controllerAs: "vm",
bindings: {
resolvedUser: "<"
},
controller: function(ImageService, $state){
const vm = this;
const resolvedUser = this.resolvedUser;
resolvedUser ? vm.user = resolvedUser : $state.go("404");
vm.$onInit = function(){
ImageService.findByName(vm.user.pokemon.name)
.then(function(res){
vm.user.pokemon.id = res.id;
vm.user.pokemon.image = res.sprites.front_default;
vm.user.pokemon.type = res.types[0].type.name;
})
.catch(function(res){
vm.user.pokemon.image = "https://www.native-instruments.com/forum/data/avatars/m/328/328352.jpg?1439377390";
});
}
}
});
})();
And here is the relevant parts from my spec file. I've made a comment where the test is failing:
describe("profile.component", function(){
var profileComponent, ImageService, $q, $httpBackend, $state, resolvedUser, jazzSpy, IS,
API = "http://pokeapi.co/api/v2/pokemon/";
var RESPONSE_SUCCESS = // very large variable I've omitted for brevity.
beforeEach(angular.mock.module("app"));
beforeEach(angular.mock.module("ui.router"));
beforeEach(inject(function(_ImageService_, _$q_, _$httpBackend_, _$state_, _$rootScope_){
ImageService = _ImageService_;
$q = _$q_;
$httpBackend = _$httpBackend_;
$state = _$state_;
$rootScope = _$rootScope_;
$rootScope.$new();
}));
describe("profileComponent with a valid user and valid Pokemon", function(){
beforeEach(inject(function(_$componentController_){
singleUser = { id: 2, name: "Erlich Bachman", email: "erlich#aviato.com", phone: 4155552233, pokemon: { isPresent: true, name: "celebi"}, icon: { isPresent: false, name: null} };
let bindings = {resolvedUser: singleUser, ImageService: ImageService, $state: $state };
profileComponent = _$componentController_("profileComponent", { $scope: {} }, bindings);
profileComponent.$onInit();
}));
beforeEach(function(){
spyOn(ImageService, "findByName").and.callThrough();
});
it("should set state to resolvedUser", function(){
expect(profileComponent.user).toEqual(singleUser);
});
it("should expect ImageService to be defined", function(){
expect(ImageService.findByName).toBeDefined();
});
it("should call ImageService.findByName() and return Pokemon icon", function(){
expect(profileComponent.user.pokemon.name).toEqual("celebi");
$httpBackend.whenGET(API + "celebi").respond(200, $q.when(RESPONSE_SUCCESS));
$httpBackend.flush();
// This is where the test fails
expect(ImageService.findByName).toHaveBeenCalledWith("celebi");
});
});

As mentioned before, $onInit needs to be called after spyOn:
beforeEach(function(){
spyOn(ImageService, "findByName").and.callThrough();
profileComponent.$onInit();
});

Related

vuejs not rending data 2nd time when data as updated using ajax

Im using vue js to update a few things on my page, its real simple use case
Vue.component('my-component', {
template: '.....<a v-on:click="myfunction">data</a>{{stuff}}'
data: {
stuff: 0
}
mounted(){
let __this = this;
axios.....then(function (data){ __this.stuff = 1l }); // works
}
methods: {
myfunction: function(){
this.stuff = 2; /// dosnt work. template not rendered
}
}
});
Any particular way to set the variable to detect changes or any pointers ? thanks.
For reusable components, your data field should actually be a function that returns the data object:
Vue.component('my-component', {
template: '.....<a v-on:click="myfunction">data</a>{{stuff}}',
data() {
return {
stuff: 0
};
},
mounted(){
let __this = this;
axios.....then(function (data){ __this.stuff = 1l }); // works
},
methods: {
myfunction: function(){
this.stuff = 2; // should work now
}
}
});
Please review the relevant section of the Vue.js documentation for more information concerning this issue.

Vue.js 2.0 - Passing arguments in methods AJAX Axios

I need to pass an arguments in methods using ajax axios.
var app = new Vue({
el: '#app',
data: {
urlAdmission:
admissions: [
{ name : 'asdf'},
{ name : 'sd'}
]
},
mounted: function(){
this.allAdmissions()
},
methods: {
allAdmissions: _.debounce( function(){
var app = this
axios.get('http://localhost/school/api/hello')
.then( function(response ){
app.admissions = response.data.admissions
})
.catch( function(error){
console.log(error)
})
})
}
});
As you can see in mounted I call the methods this.allAdmissions() I need to pass an argument so that I can reuse the function. For example this.allAdmissions('http://localhost/school/api/hello'). Then use it in axios.get('url'). Thanks
It looks like what you're trying to do is make a function that can accept a url and bind the results of the url to a variable value in your data. Here is how you might do that.
methods: {
allAdmissions: _.debounce(function(url, value){
axios.get(url)
.then(function(response){
this[value] = response.data.admissions
}.bind(this))
.catch(function(error){
console.log(error)
})
})
}
Then, if you call that method like this,
this.allAdmissions('http://localhost/school/api/admissions‌​', "admissions")
allAdmissions will set the admissions property on your data to the result of your call. This works if you always want to use response.data.admissions because you hardcoded that. If you wanted that to be variable as well, you might pass in a third value like so
methods: {
getSomeData: _.debounce(function(url, value, responseValue){
axios.get(url)
.then(function(response){
this[value] = response.data[responseValue]
}.bind(this))
.catch(function(error){
console.log(error)
})
})
}
In case some will need multiple ajax request. Here is an example.
var app = new Vue({
el: '#app',
data: {
value: '',
admissions: [],
schoolyear: []
},
created: function(){
this.ajaxAll()
},
methods: {
ajaxAll: _.debounce( function(){
var app = this
var admissions = 'admissions'
var schoolYear = 'schoolyear'
axios.all([this.getAllData('http://localhost/school/api/admissions', 'admissions'), this.getAllData('http://localhost/school/api/schoolyear', 'schoolyear')]);
}),
getAllData: function(url, value){
var app = this
return axios.get(url)
.then(function(response){
app[value] = response.data[value]
console.log(response.data.admissions)
})
}
}
})
Credit to #Bert Evans.

react-flux application error - unable to find the function even if it's defined

I have a react component - coursePage.js
function getCourseInitState(){
return {
courses: CourseStore.getAllCourses()//courseStore is required in script
};
}
var Courses = React.createClass({
getInitialState: function(){
return getCourseInitState();
},
render: function () {
return (
<div>
<h1> Course </h1>
<CourseList courses={this.state.courses} />
</div>
);
}
});
Action file -courseAction
var CourseAction = {
CourseList: function(){
var courseList = CourseApi.getAllCourses();
Dispatcher.dispatch({
actionType: ActionTypes.COURSE_INITIALIZE,
courseList: courseList
});
}
Store File - courseStore
var CourseStore = assign({}, EventEmitter.prototype, {
addChangeListener: function(callback){
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback){
this.removeListener(CHANGE_EVENT, callback);
},
emitChange: function(){
this.emit(CHANGE_EVENT);
},
getAllcourses: function(){ //here is the function define
return _courses;
},
getCourseById: function(id){
return _.find(_courses, {id: id});
}
});
Dispatcher.register(function(action){
switch(action.actionType){
case ActionTypes.COURSE_INITIALIZE:
_courses = action.CourseList;
CourseStore.emitChange();
break;
}
});
module.exports = CourseStore;
in console I am getting "Uncaught TypeError: CourseStore.getAllCourses is not a function"
I don't want to call api directly in my coursePage.js so I find this way of initialising the page but it is not working.
(Please note - I am new to this) As per my recent learning Action file must always call API and send the request to State. I can load with help of componentWillMount function. But, I wanted to solve with this.If not wrong, then it is more neat and preferable way of implementing?
You have a typo -> getAllcourses in the Store and in the Component you call getAllCourses
getAllCourses: function(){ //Should be getAllCourses instead of getAllcourses
return _courses;
},

Unit-testing remote methods of a strongloop loopback.io model

I am trying to write unittests for a loopback model using jasmine. My model has the usual CRUD endpoints but I have defined a custom '/products/:id/upload' endpoint which expects a form with files.
My model looks like
'use strict';
var loopback = require('loopback');
var ProductSchema = {
location: {
type: String,
required: true
},
version: {
type: String,
required: true
},
id: { type: Number, id: 1, generated: true }
};
var opts = {
strict: true
};
var dataSource = loopback.createDataSource({
connector: loopback.Memory
});
var Product = dataSource.createModel('Product', ProductSchema, opts);
Product.beforeRemote('upload', function(ctx){
var uploader = function(req, res){
// parse a multipart form
res({
result:'success'
});
};
function createProduct(uploaderResult){
// create a product out of the uploaded file
ctx.res.send({
result: uploaderResult.result
});
}
uploader.upload(ctx.req, createProduct);
});
Product.upload = function () {
// empty function - all the logic takes place inside before remote
};
loopback.remoteMethod(
Product.upload,
{
accepts : [{arg: 'uploadedFiles', http: function(ctx){
return function() {
return { files : ctx.req.body.uploadedFiles, context : ctx };
};
}},
{arg: 'id', type: 'string'}],
returns : {arg: 'upload_result', type: String},
http: {path:'/:id/upload', verb: 'post'}
}
);
module.exports = Product;
My end goal is to test the logic of the "createProduct".
My test looks like
'use strict';
describe('Product Model', function(){
var app = require('../../app');
var loopback = require('loopback');
var ProductModel;
beforeEach(function(){
app = loopback();
app.boot(__dirname+'/../../'); // contains a 'models' folder
ProductModel = loopback.getModel('Product');
var dataSource = loopback.createDataSource({
connector: loopback.Memory
});
ProductModel.attachTo(dataSource);
});
it('should load file ', function(){
console.log(ProductModel.beforeRemote.toString());
console.log(ProductModel);
ProductModel.upload();
});
});
By calling ProductModel.upload(); I was hoping to trigger the before remote hook which would exercise the the createProduct. I could test "createProduct" in isolation but then I would omit the fact that createProduct ends up being called as a result of upload.
To be perfectly clear, the core question is:
How do I exercise remote method hooks inside unittests ?
It was suggested to use supertest as an http server. Below there is a code snippet illustrating how to do it in jasmine
describe('My product suite', function(){
var request = require('supertest');
var app;
beforeEach(function(){
app = loopback();
// don't forget to add REST to the app
app.use(app.rest());
});
it('should load file', function() {
request(app).post('/products/id-of-existing-product/upload')
.attach('file', 'path/to/local/file/to/upload.png')
.expect(200)
.end(function(err, res) {
if (err) return done(err);
// res is the HTTP response
// you can assert on res.body, etc.
});
});
});

backbone collection add does not trigger model validate

I am rather new to backbone and wanted to test a simple script that handles a to do list. Here is the code i used so far:
(function() {
window.App = {
Models: {},
Collections: {},
Views: {}
};
window.template = function(id) {
return _.template($('#' + id).html());
}
App.Models.Task = Backbone.Model.extend({
validate: function(attributes) {
if ( !$.trim(attributes.title) ) {
return 'Invalid title';
}
}
});
App.Collections.Tasks = Backbone.Collection.extend({
model: App.Models.Task
});
App.Views.Task = Backbone.View.extend({
tagName: 'li',
template: template('taskTemplate'),
initialize: function () {
this.model.on('change', this.render, this);
this.model.on('destroy', this.remove, this);
},
events: {
'click .edit': 'editTask',
'click .delete': 'destroy'
},
destroy: function() {
if (confirm('Are you sure?')) {
this.model.destroy();
}
},
remove: function() {
this.$el.remove();
},
editTask: function() {
var newTaskTitle = prompt('New title:', this.model.get('title'));
this.model.set('title', newTaskTitle, {validate: true});
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
App.Views.AddTask = Backbone.View.extend({
el: 'form#addTask',
initialize: function() {
},
events: {
'submit': 'submit'
},
submit: function(event) {
event.preventDefault();
var newTaskTitle = $(event.currentTarget).find('input[type=text]').val();
var task = new App.Models.Task({ title: newTaskTitle });
this.collection.add(task, {add: true, merge: false, remove: false});
}
});
App.Views.Tasks = Backbone.View.extend({
tagName: 'ul',
initialize: function() {
this.collection.on('add', this.addOne, this);
},
render: function() {
this.collection.each(this.addOne, this);
return this;
},
addOne: function(task) {
var taskView = new App.Views.Task({ model: task });
this.$el.append(taskView.render().el);
}
});
var tasks = new App.Collections.Tasks([
{
title: 'Go to store',
priority: 4
},
{
title: 'Go to mall',
priority: 3
},
{
title: 'Get to work',
priority: 5
}
]);
var addTaskView = new App.Views.AddTask({ collection: tasks });
var tasksView = new App.Views.Tasks({ collection: tasks });
$('div.tasks').append(tasksView.render().el);
})();
So the model validation works fine ... the only pb is that collection.add does not validate the newly added model .... is the a way to force the validation?
Thanks,
Rares
From the fine manual:
validate model.validate(attributes, options)
[...] By default validate is called before save, but can also be
called before set if {validate:true} is passed.
Collection#add does not call save nor does it call set with the validate: true option. If you want to validate during add, say so:
collection.add(models, { validate: true });
That will get validate:true all that way down to Model#set.
A quick look at a simplified example may be helpful:
var M = Backbone.Model.extend({
set: function() {
console.log('setting...');
Backbone.Model.prototype.set.apply(this, arguments);
},
validate: function() {
console.log('validating...');
return 'Never!';
}
});
var C = Backbone.Collection.extend({
model: M
});
var c = new C;
c.on('add', function() {
console.log('Added: ', arguments);
});
c.on('invalid', function() {
console.log('Error: ', arguments);
});
Now if we do this (http://jsfiddle.net/ambiguous/7NqPg/):
c.add(
{ where: 'is', pancakes: 'house?' },
{ validate: true }
);
You'll see that set is called with validate: true, validate will be called, and you'll get an error. But if you say this (http://jsfiddle.net/ambiguous/7b2mn/):
c.add(
{ where: 'is', pancakes: 'house?' },
{add: true, merge: false, remove: false} // Your options
);
You'll see that set is called without validate: true, validate will not be called, and the model will be added to the collection.
The above behavior is quite strongly implied but not explicitly specified so you may not want to trust it. Model#initialize does say:
you can pass in the initial values of the attributes, which will be set on the model.
and set does explicitly mention the validate option. However, there is no guarantee that Collection#add will send options to the model constructor or set or that the model's constructor will send the options to set. So if you want to be really paranoid and future proof, you could add a quick check for this "options get all the way down to set" behavior to your test suite; then, if it changes you'll know about it and you can fix it.
if you pass options to your collection add method, the validation method will not be called and as your arguments in this case are all set to the default value, there is not need to pass them
this.collection.add(task);
you may want to take a look at this question.
Prevent Backbone.js model from validating when first added to collection

Resources