callThrough not working when creating spy using createSpyObj - javascript

It seems the and.callThrough does not actually run the original implementation, when the spy object is created using createSpyObj. As an example, let say I have an Angular service that has the following helper method:
user.service.ts:
class UserService {
getFullName(first: string, last: string): string {
return first + ' ' + last;
}
}
In the specs, I create a spy using createSpyObj:
user.service.specs.ts
const userServiceSpy = jasmine.createSpyObj<UserService>('UserService', ['getFullName']);
// ....
beforeEach(() => {
userServiceSpy.getFullName.and.callThought();
const test = userService.getFullName('test1', 'test2');
console.log(test); // < = = = = = ISSUE: test is undefined! WHY ???
});
How can I make the getFullName function run as implemented in the main class?
(I don't want to stub, or call a fake function, but somehow use the main function implementation when calling the getFullName).
I tried overwriting the function prototype with the original:
userServiceSpy.getFullName.prototype = UserService.prototype.getFullName;
Second attempt:
userServiceSpy.getFullName = UserService.prototyp.getFullName; // This is a compile error.

We need to use callFake and use the function implementation from the prototype as follows:
userServiceSpy.getFullName.and.callFake(UserService.prototype.getFullName);

Related

Assigning a name to an anonymous function (.name vs .displayName)

I am running a react native project, where this function
const f = function() {};
has an undefined name, not inferred.
Keeping the anonymous definition, is this code:
f.name = "f";
console.log(f.name); // f
an anti-pattern? Should I use .displayName instead?
Update (real code, the described one is a simpler version):
const MyComponent = forwardRef(({ userId }, ref) => {
useTrackScreenView(MyComponent.name, {
user_id: userId,
});
return JSX...
});
console.log(MyComponent.name) // undefined
//
// MyComponent.name = "MyComponent"; anti-pattern?
// or
// MyComponent.displayName = "MyComponent";
//
Using the #ts-check directive indicates that the Function's .name property is read-only.
So, the way to go is using .displayName.

Why can’t I access a property on “this” in a class via its prototype?

I wrote this class and have set up an array property for it. Then, I want to add an item to this array.
However, when I try to do it, I get the error “Uncaught TypeError: Cannot read property push of undefined”.
Isn’t this possible?
class test {
constructor() {
this.myArray = [];
}
myMethod() {
this.myArray.push("ok");
}
};
console.log(test.prototype.myMethod());
That’s not how classes are used. You need to instantiate test first, by using new test(). The constructor was never called in your case, so this.myArray was never defined.
This is the only way this can work:
let testInstance = new test();
testInstance.myMethod();
This way, the constructor is called and there will be no error.
Of course, next you’ll need some way of retrieving your array, in order to see the effect.
try to create the instance first. See the code I've commented it in details
test.prototype = {
constructor: test,
myMethod: function() {
this.myArray.push("ok");
}
};
var test = function(){
this.myArray = [];
}
test.prototype = { // add our custom constructor and custom methods
constructor: test,
myMethod: function() {
this.myArray.push("ok");
}
};
var myVar = new test(); // create new instance of test
myVar.myMethod(); // run custom method to push val
console.log( myVar.myArray );
You need to initiate your class test first.
var t = new test();
For your information:
console.log(test.prototype.myMethod());
will give you "undefined". Try e.g. :
var t = new test();
t.myMethod();
console.log(t.myArray);
to get output similar to this:
Array [ "ok" ]

Add method dynamically/Selective override prototype method in Flow

I have a constructor like this:
function IDBCrud(table: string): void {
...
}
IDBCrud.prototype.get = function(...) { ... }
IDBCrud.prototype.post = function(...) { ... }
And it using like this:
const accounts = new IDBCrud('Accounts');
accounts.get( ... );
accounts.create( ... );
But sometimes, I want to define method to object directly with same name as in property, so that invokes instead of prototype's method.
// Override get method for some reason
accounts.get = function( ... ) {
// Do some stuffs...
...
// Now call prototype get
return this.__proto__.get.apply(this, arguments);
}
But when I ran flow, it fails with this:
16: accounts.get = function(match, options) {
^^^ property `get`. Property not found in
16: accounts.get = function(match, options) {
^^^^^^^^^^^^ new object
Because IDBCrud doesn't have "get" property(or method). But if I just write them with empty value like this:
function IDBCrud(...): ... {
this.get = function() {};
this.create = function() {};
...
}
If should be work in that case, but if do that, I have to redefine every "get" method to invoke prototype's get method.
const accounts = new IDBCrud('accounts');
accounts.get = function() { ... }; // Override
accounts.get(); // works
const users = new IDBCrud('users');
users.get(); // Invokes users.get and it's empty function, instead of prototype.get
I don't wanna do that everytime I made IDBCrud instance, I just want to override it only it needed.
Without flow, it's not a problem, but with it, it fails.
So how do I achieve this with flow? Any advice will very appreciate it.
Override it only over the object instances where you want to achieve a different behavior:
function IDBCrud(table){
}
IDBCrud.prototype.get = function() { console.log('get1'); }
var a = new IDBCrud();
a.get(); // get1
a.get = function() { console.log('get2'); }
a.get(); // get2
var b = new IDBCrud();
b.get(); // get1
Flow was intentionally built for supporting es6 class, and it blocks me to add method in runtime for safety reasons.
Solution was simple, convert constructor to class and make new class that extends IDBCrud and override method and it's working now.

Using SinonJS stub (with rewire)

I have a function:
var publish = function(a, b, c) {
main = a + getWriterName(b,c);
}
and getWriterName is another function:
var getWriterName = function(b,c) {
return 'Hello World';
}
I want to test the "publish" function but I do not want to run the "getWriterName" function while I am testing "publish". I feel like I stub getWriterName function because I don't want to run it everytime I test "publish", but how do I do that? I did something like:
var sandbox = sinon.sandbox.create();
sandbox.stub(getWriterName).returns('done');
But this gives me an error of
TypeError: Attempted to wrap undefined property undefined as function
What is wrong with my stubbing if I am in the write path?
Edit:
I am using rewire so would like solutions using rewire
This is how Sinon can be used with Rewire to stub a function. Rewire in this case is particularly useful if the stubbed function is private.
it('getWriteName always returns "Hello World"', function() {
var stub = sandbox.stub();
stub.returns('Hello World');
var unset = log.__set__('getWriterName', stub);
// your test and expectations here
unset();
// it's always good to restore the previous state
});
This solved my problem:
If my functions are in a file called main.js then firstly I'd rewire the file as:
var main = rewire('main');
Then to stub any other function being called in one function, in my case, when I had to stub getWriterName I'd do:
main.__set__('getWriterName', function(b, c) {
return 'Something Else'
}
and Finally after finishing using it, do
main.restore();
From sinon docs: "The sinon.sandbox.create(config) method is mostly an integration feature, and as an end-user of Sinon.JS you will probably not need it."
Normally you create a sinon stub with the syntax:
sinon.stub(obj, 'property, function(){
//do something
}
Let's say that somewhere in your file you are exporting these two functions
//somefile.js
var publish = function(a, b, c) {
main = a + getWriterName(b,c);
}
var getWriterName = function(b,c) {
return 'Hello World';
}
exports.getWriterName = getWriterName;
exports.publish = publish;
Importing them in your tests:
var someFile = require('./somefile.js');
And trying to stub out the method you'd like to:
sinon.stub(someFile, 'getWriterName', function(b, c) {
return 'done'
});
You'll find that this too won't work. This is because sinon can't actually stub out a method that has been required unless it can access it as a property of the file you required. In order for this to work, you'd need to be doing this:
//somefile.js
var getWriterName = function(b,c) {
return 'Hello World';
}
exports.getWriterName = getWriterName;
var publish = function(a, b, c) {
main = a + exports.getWriterName(b,c);
}
exports.publish = publish;
Now the getWriterName is accessible for stubbing out once you import the file with the functions into your tests. You'd do it just like the above example:
sinon.stub(someFile, 'getWriterName', function(b, c) {
return 'done'
});
And can undo it with:
someFile.getWriterName.restore();

Spy on a constructor using jasmine

I want to spy on a constructor function and tell how many times it's been called using jasmine. I'd usually do something like this to target a method of an object:
spyOn(lib,'methodName')
but in the case I'm trying to spy on the actualy constructor so I've tried:
spyOn(lib);
it('lib should be instantiated for each matching element', function () {
spyOn(lib);
expect(lib.calls.count()).toEqual(2);
});
Unfortunately this just gives me an error in the console:
"Error: undefined() method does not exist in ..."
how can I spy on the constructor?
The spyOn() function can only replace object properties, so the only thing you can do is spying on the prototype. Now if you would spy on the prototype of the real class, it would interfere with the other tests, so you have to use prototypal inheritance.
You can do something like this :
var mockClass = function (Subject) {
var Surrogate = function () {
Surrogate.prototype.constructor.apply(this, arguments);
};
Surrogate.prototype = Object.create(Subject.prototype);
Surrogate.prototype.constructor = Subject;
return Surrogate;
};
Some tests:
var My = function (a) {
this.init(a);
};
My.prototype = {
init: function (a) {
this.setA(a);
},
setA: function (a) {
this.a = a;
}
};
var Mock = mockClass(My);
spyOn(Mock.prototype, "constructor").andCallThrough();
spyOn(Mock.prototype, "init");
var m = new Mock(1);
expect(Mock.prototype.init).toBe(m.init);
expect(My.prototype.init).not.toBe(m.init);
expect(m.constructor).toHaveBeenCalledWith(1);
expect(m.init).toHaveBeenCalledWith(1);
expect(m.a).toBeUndefined();
m.setA(1);
expect(m.a).toBe(1);
spyOn(Mock.prototype, "setA").andCallFake(function (a) {
this.a = a + 1;
});
m.setA(1);
expect(m.setA).toHaveBeenCalledWith(1);
expect(m.a).toBe(2);
You cannot spy on the constructor if your code uses an x.constructor based type checking. But I think this can happen only by integration tests, and by badly designed codes...

Categories

Resources