I'm using the superagent ajax library for an app, and I'm trying to write some unit tests for it. I've got a class which looks like this:
someClass = {
getData: function(){
_this = this;
superagent.get('/some_url').end(function(res){
if(res.body){
_this.data = res.body
}
});
});
}
How do I write a Jasmine test to spy on the _this.data = res.body call? Setting up a spy with and.callThrough() on getData isn't working. I don't want to actually call the URL in question; I'm just trying to test that if it gets data, it does something with it.
Thanks
spyOn(superagent, 'get').and.callFake(function(url) {
return {
end: function(cb) {
//null for no error, and object to mirror how a response would look.
cb(null, {body: data});
}
}
});
Bror's answer works perfectly. To add something to his answer, when we need to add another superagent function (like set) to the spyOn method, you can use as follows.
spyOn(superagent, 'get').and.callFake(function(url) {
return {
set: function() {
return {
end: function(cb) {
//null for no error, and object to mirror how a response would look.
cb(null, {body: data});
}
}
}
});
Here, the set function is used to set headers to the request.
There's another good solution here, which is to abstract out the anonymous function:
someClass = {
getData: function(){
_this = this;
superagent.get('/some_url').end(this.handleAjax);
},
handleAjax: function(res){
if(res.body){
_this.data = res.body
}
}
}
Now you can test the handleAjax function discretely and with simple tests; and also stub superagent as you only need to check a method .end() is called on it with a particular value.
Anonymous functions are problematic for other reasons than just testing so this is a good refactor
Say it's a patch, first make a mock patch return value:
this.mockPatchObject = {
set: () => {
return {
end: (cb: any) => {
cb(null, {body: 'etc'});
},
};
},
};
then return it as the value of patch:
this.superagentSpy = spyOn(request,'patch').and.returnValue(this.mockPatchObject);
then spy on the set function of the "mock" patch object:
this.superagentSetSpy = spyOn(this.mockPatchObject, 'set');
Related
Scenario
I'm trying to do multiple it specs on a single external load rather than have the external data loaded EVERY time.
Question
How can I do this with a single call of getExternalValue while still keeping my it definitions?
Ideas
Currently I'm doing all the expects in a single it block. I've also thought about storing the loaded value before my tests but then I'd have to find another way to make jasmine wait until the value is loaded.
Code
function getExternalValue(callback) {
console.log("getting external value");
setTimeout(function() {
callback(true);
}, 2000);
return false;
}
describe("mjaTestLambda()", function() {
it("is truthy", function(done) {
let truthy;
truthy = getExternalValue(function(bool) {
truthy = bool;
expect(truthy).toBeTruthy();
done();
});
});
it("is falsy", function(done) {
let truthy;
truthy = getExternalValue(function(bool) {
truthy = bool;
expect(!truthy).toBeFalsy();
done();
});
});
});
How can I do this with a single call of getExternalValue while still
keeping my it definitions?
Use beforeEach() or beforeAll() to get the resolved value. Personally I suggest beforeEach() as it will reset the value for each test and helps ensure a clean setup for each one.
I noticed your function has a callback parameter. Async/await is a useful pattern that works best when (1) you're writing async/await functions or (2) your functions return a Promise. If you need to keep the callback parameter, let me know and I'll update the following:
// returns Promise
function getExternalValue() {
return new Promise((resolve, reject) => {
console.log("getting external value");
setTimeout(() => {
resolve(true);
}, 2000);
});
}
describe("mjaTestLambda()", () => {
let value;
beforeAll(() => {
return getExternalValue()
.then((v) => { value = v; });
});
it("is truthy", () => {
expect(v).toBeTruthy();
});
it("is not falsy", () => {
expect(!v).toBeFalsy();
});
});
I've been working on this app for a while. I have several other modules that all work fine. I've been having a ton of trouble with this particular module and it's super frustrating. This problem looks super simple. Maybe I'm over thinking it. Hopefully someone will say that I am. :)
In this module, I decided to use methods from my model. This particular one is non-instanced. Here is my model:
/*
* Account.js
*/
module.exports = {
connection: 'islMongo',
attributes: {
name: {
type: 'string',
required: true,
},
},
numberToName: function(accountNumber) {
Account.findOne(accountNumber).exec(function(err, a){
if (err) {
return 'err';
} else {
return 'ok';
}
});
return 'broke';
},
};
I call it from one of my controllers like this:
var accountName = Account.numberToName(params.id);
At this point accountName's value is "broke". I don't understand why it wouldn't either return "err" or "ok". I simplified my actual function here for testing.
Edit:
I have other calls that work properly. For instance:
updateBalance: function(account, amount, callback) {
/* Accepts account id or account object */
(function _lookupAccount(afterLookup) {
if (typeof account === 'object') return afterLookup(null, account);
Account.findOne(account)
.exec(afterLookup);
})(function (err, a) {
if (err) return callback(err);
if (!a) {
err = new Error();
err.message = "Couldn't find account.";
err.status = 400;
return callback(err);
}
a.balance = parseInt(a.balance) + parseInt(amount);
a.save(callback);
});
},
Is called like this:
Account.updateBalance(params.account, -2000);
The definition has a callback, but I don't actually use one because it isn't needed. The method works fine.
Sails.js documentation provides example methods that don't use callbacks. They simply return the requested data.
// Attribute methods
getFullName: function (){
return this.firstName + ' ' + this.lastName;
},
isMarried: function () {
return !!this.spouse;
},
isEligibleForSocialSecurity: function (){
return this.age >= 65;
},
encryptPassword: function () {
}
And called like this:
if ( rick.isMarried() ) {
// ...
}
Which is what I am trying to do with my method at the top of this post. It seems like the exec() portion of Account.findOne() isn't even being called.
Sails.js & Node.js are asynchronous. So in simple words they don't wait for response from database, but when they got date they call a callback. So you need to read about Queries and callbacks and what is callback hell (you should never do that).
And now get back to your problem.
/*
Account.js
*/
//...
numberToName: function(accountNumber, callback) {
// if you want some additional logic you can create function here and call callback in it
Account.findOne(accountNumber).exec(callback);
}
//...
Tip: callbacks first param is always error.
// AccountController
method: function(req, res){
var id = req.param('id'); // if its int you should parseInt()
var callback = function(error, account){
if(error)
res.send('error');
else
res.send(account.name);
};
Account.numberToName(id, callback);
}
I'm try to learn this tech and somehow getting stuck at the opening.
Please tell me why this test isn't working. What obvious thing did I miss?
var myfunc = function() {
alert('hello');
}
test("should spy on myfunc", function() {
var mySpy = sinon.spy(myfunc);
myfunc();
sinon.assert.calledOnce(mySpy);
});
It's the scope of myfunc. This works:
var o = {
myfunc: function() {
alert('hello');
}
};
test("should spy on myfunc", function() {
var mySpy = sinon.spy(o, "myfunc");
o.myfunc();
sinon.assert.calledOnce(mySpy);
ok(true);
});
The reason your test is not working is because you're not invoking the spy, rather the original function.
And the reason #carbontax's example works is because in that case, o.myfunc is replaced by the spy automatically; so when you invoke o.myfunc, you're actually invoking the spy.
As Mrchief said, you are not invoking spy but calling myfunc();, you should invoke spy something like.
test("should spy on myfunc", function() {
var mySpy = sinon.spy(myfunc);
mySpy(); // <= should called instead of myfunc()
sinon.assert.calledOnce(mySpy);
});
I've got problem with my controller
Ext.define('app.controller.myController', {
init: function() {
this.control({
'#myinputfield': {
change: this.textFieldChange(parameter)
}
})
},
textFieldChange: function(parameter) {
//do something
}
});
its look like this
the problem is when i give parameter here
change: this.textFieldChange(parameter)
then its fire up after site load and I don't know why.
without parameter its waiting for change event like it should
can any1 help me please ?
It is because:
change: this.textFieldChange here you are giving the reference for this function to this property
change: this.textFieldChange(parameter) here you are giving the result of the function to this property (which if we don't use return, then it will be undefined).
You can use the eOpts variable in the function definition, for custom parameter sending, see in Example:
Ext.define('app.controller.myController', {
init: function() {
this.control({
'#myinputfield': {
change: {
fn : this.textFieldChange,
params : {
param1: 'something'
}
}
}
})
},
textFieldChange: function(textfield, newValue, oldValue, eOpts) {
var params = eOpts.params;
console.log(params.param1);
//do something
}
});
I have a controller and factory defined as below.
myApp.controller('ListController',
function($scope, ListFactory) {
$scope.posts = ListFactory.get();
console.log($scope.posts);
});
myApp.factory('ListFactory', function($http) {
return {
get: function() {
$http.get('http://example.com/list').then(function(response) {
if (response.data.error) {
return null;
}
else {
console.log(response.data);
return response.data;
}
});
}
};
});
What confuses me is that I get the output undefined from my controller, and then the next line of console output is my list of objects from my factory. I have also tried changing my controller to
myApp.controller('ListController',
function($scope, ListFactory) {
ListFactory.get().then(function(data) {
$scope.posts = data;
});
console.log($scope.posts);
});
But I receive the error
TypeError: Cannot call method 'then' of undefined
Note: I found this information on using a factory through http://www.benlesh.com/2013/02/angularjs-creating-service-with-http.html
You need to either use a callback function or just put a return before $http.get...
return $http.get('http://example.com/list').then(function (response) {
if (response.data.error) {
return null;
} else {
console.log(response.data);
return response.data;
}
});
$http.get is asynchronous so at the time you try to access it (inside your controller) it may not have data (hence you get undefined).
To solve this I use .then() after I call the factory method from my controller. Your factory then would look something like:
myApp.factory('ListFactory', function($http) {
return {
get: function() {
$http.get('http://example.com/list');
}
};
});
And your controller:
myApp.controller('ListController', function($scope, ListFactory) {
ListFactory.get().then(function(response){
$scope.posts = response.data;
});
// You can chain other events if required
});
Hope it helps