How spy works with Jasmine in Angular 7? - jasmine

I have created this spy using spyOn
it("spyon ", () => {
const searchChangeEmitSpy = spyOn(Adders.countlist,"add");
expect(searchChangeEmitSpy.calls.count()).toEqual(2);
});
and inside Adder class I have the following function
countlist(){ const i =0;
this.quoteList.forEach(element => {
console.log(element);
this.add(4,i++);
});
}
length of quoteList array is 2
what I am getting as a result
Error: : add() method does not exist

I don't think you can directly spy on the function of the class Adders like this, instead spy on the prototype or create an instance of the class and spy on that. I would use two spies and implement it like this:
it("spyon", () => {
const countlistSpy = spyOn(Adders.prototype, 'countlist');
const addSpy = spyOn(Adders.prototype, 'add');
// call your function / trigger something that calls the function
expect(countlistSpy).toHaveBeenCalledTimes(1);
// more expectations here
});
Or with an instance of the class in the beforeEach block you can define your instance like this:
let adder: Adders = new Adders();
And then your test would look like this:
it("spyon", () => {
const countlistSpy = spyOn(adder, 'countlist');
const addSpy = spyOn(adder, 'add');
// call your function / trigger something that calls the function
expect(countlistSpy).toHaveBeenCalledTimes(1);
// more expectations here
});

With the help of Fabian answer, I able to debug and solve my problem. Actually, I need to trigger the function inside the class on which I was spying. after doing so, it gave me the expected output.
test case
it("spyOn countList add()", () => {
const searchChangeEmitSpy = spyOn(Adders,"add");
Adders.addNewQuote("This is my second post");
Adders.countlist(0);
expect(searchChangeEmitSpy.calls.count()).toEqual(2);
});
function inside the class to be spied
countlist(i:number){
this.quoteList.forEach(element => {
console.log(element);
this.add(4,i++);
});
//return i;
}

Related

Is there a simple way for to check if method was not called when a variable is undefined?

So I have a simple method that executes something only if the passed variable is defined:
public myFunction(item) {
if (typeof item !== 'undefined') {
item.doSomething();
}
}
Here's my test in jasmine:
describe('myFunction()', () => {
it ('should only do something if the item passed is defined.', () => {
const item = new Item();
spyOn(item, 'doSomething');
service.myFunction(item);
//this works
expect(item.doSomething).toHaveBeenCalledTimes(1);
});
it ('should not do something if the item passed is undefined.', () => {
const item = undefined;
spyOn(item, 'doSomething');
service.myFunction(item);
//this does not work..
expect(item.doSomething).toHaveBeenCalledTimes(0);
});
});
My first test works fine. But I do not know how to express my second test. How can I say that doSomething was never called when the item passed is undefined? It seems quite trivial, but I'm having trouble with this. I have a feeling it's not possible, because I can't spy on something that is undefined. Then again, maybe there is a work around?
Try:
it ('should not do something if the item passed is undefined.', () => {
const item = undefined;
const conditionForIf = typeof item !== 'undefined';
// check the conditionForIf, if it is false, it won't go on and `doSomething`
expect(conditionForIf).toBe(false);
});

How to test the code logic of ngOnInit in Jasmine and Angular

My component looks for presence of a parameter in the route in ngOnInit. If the parameter is not present, it shows error. I want to test this logic.
ngOnInit() {
this.id = this.route.snapshot.paramMap.get("id");
if(this.id != null) {
... } else{
console.log("didn't get id from the route");
this.showDialog(...);
}
}
I wrote the following spec. In the spec, the parameter is not passed in the route
beforeEach(async() => {
fixture = TestBed.createComponent(QuestionDetailsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
fit('should show error if question details of a question can\'t be retrieved', () => {
spyOn(component,'showDialog');
expect(componetn.showDialog).toHaveBeenCalled();
});
but my test case fails with reason Expected spy showDialog to have been called.
I suppose the issue is that showDialog gets called when the component gets created before the it is called.
How do I test the logic in ngOnInit? I need the component before I can test it (i.e. call it) and I want to test the logic which gets executed while the component is being created.
To test the ngOnInit method, you just need to call it:
component.ngOnInit();
And the route value can be spied:
spyOn(component.route.snapshot.paramMap,"get").and.returnValue("some_id");
Also, you can change the returned value. For example:
fit("should ...", () => {
let mock_id = null;
spyOn(component,"showDialog");
spyOn(component.route.snapshot.paramMap,"get").and.callFake(() => {
return mock_id;
});
component.ngOnInit();
expect(componetn.showDialog).toHaveBeenCalled();
expect(componetn.showDialog).toHaveBeenCalledTimes(1);
mock_id = "some_value";
component.ngOnInit();
expect(...).to..
...
expect(componetn.showDialog).toHaveBeenCalledTimes(1);
mock_id = "another_value";
component.ngOnInit();
expect(...).to..
...
expect(componetn.showDialog).toHaveBeenCalledTimes(1);
});

RXJS Observable - How to call next from outside of the Observable's constructor

I am building a service which exposes an Observable. In this service I receive external function calls which should trigger a next call on the Observable so that various consumers get the subscribe event. During Observer constructor I can call next and everything works great, but how can I access this outside of the constructor so that external triggers can fire next calls?
private myObservable$: Observable<any>;
During service init I do
this.myObservable$ = new Observable(observer => {
observer.next("initial message");
}
Then in other methods of the same service I want to be able to execute something like
this.myObservable$.observer.next("next message");
The above obviously doesn't work, but how can I accomplish this goal?
I'm assuming I'm missing something basic since there must be a way to emit further messages outside of the Observable's initial constructor
You should create a Subject for that
this.myObservable$ = new Subject();
And then you can call at any point:
this.myObservable$.next(...);
Or use subscribe:
this.myObservable$.subscribe(...)
Actually Subject is used for both publisher and subscriber, and here I think you need only to publish your value, so simply use Observable.
By using observable, assign Subscriber to class level variable and then use it, like below code
subscriber: Subscriber<boolean>;
public observe(): Observable<boolean> {
return new Observable<boolean>(subs => {
this.subscriber = subs;
});
}
public callNext() {
if (this.subscriber) {
this.subscriber.next();
this.subscriber.complete();
}
}
Two ways:
Make myObservable$ public:
public myObservable$: Observable;
Encapsulate the observable in a subject stream, and provide a helper to call next:
export class TestService {
public myObservable$: Observable;
private _myObservableSubject: Subject;
constructor() {
this._myObservableSubject = new Subject();
this.myObservable$ = this._myObservableSubject.asObservable();
}
public NextMessage(message?: string): void {
this._myObservableSubject.next(message);
}
}
Observable: You have to call the next() function from inside the constructor and only one time you can subscribe
message = new Observable((observer)=>{
observer.next(9);
})
this.messsage.subscribe((res)=>{
console.log(res)
})
output: 9
Subject: You have to call next() function from outside the constructor and multiple times you can subscribe.
The subject does not store any initial value before subscribe.
messsage = new Subject()
this.messsage.next(3)
this.messsage.subscribe((res)=>{
console.log(' A '+res)
})
this.messsage.next(4)
this.messsage.next(5)
this.messsage.subscribe((res)=>{
console.log(' B '+res)
})
this.messsage.next(6)
output:
A 4
A 5
A 6
B 6
BehaviorSubject: You have to call next() function from outside the constructor and multiple times you can subscribe.
The BehaviorSubject does store only one initial value before subscribe.
messsage = new BehaviorSubject ()
this.messsage.next(3)
this.messsage.subscribe((res)=>{
console.log(' A '+res)
})
this.messsage.next(4)
this.messsage.next(5)
this.messsage.subscribe((res)=>{
console.log(' B '+res)
})
this.messsage.next(6)
output:
A 3
A 4
A 5
B 5
A 6
B 6
I ended up combining a couple of things:
olsn's answer, which nicely demonstrates Subject's ease of use
Ravi's answer, which correctly points out that we only want an Observable exposed
a more functional approach, because this and Class give me the shivers
TypeScript typings for generic use
const createObservableWithNext = <T>(): {
observable: Observable<T>;
next: (value: T) => void;
} => {
const subject = new Subject<T>();
const observable = subject.asObservable();
const next = (value: T) => subject.next(value);
return {
observable,
next,
};
};

Create a spy for a function in a class

I'm adding Jasmine to a large project in order to add tests to that project's javascript. Normally I use Ruby and I'm a little out of my element here.
I have a class, that has a function and I want to create a spy for it so that it returns a certain value during one of my tests. Here's a summary of the code:
class #MyKlass
current_location = ->
window.location.host
verify_domain: () ->
domain_filter = current_location()
domain_list = /example\.com/i
#valid_domain = domain_filter.match(domain_list)?
So how would I do something like this?
it("verifies domains", function() {
spyOn(MyKlass, 'current_location').and.returnValue("example");
var myKlass = new MyKlass();
expect(myKlass.verify_domain()).toEqual(true);
});
So it turns out that this was a function on an instance of the class- which I had just missed somehow in the code. Here's the abbreviated solution:
describe('MyKlass', function() {
myKlass = null
beforeEach(function() {
myKlass = new MyKlass()
});
it('returns true for our domains', function() {
spyOn(myKlass, 'verify_domain').and.returnValue(true);
expect(myKlass.verify_domain()).toBe(true);
});

pass function to another and call by name

<a id="aHw" href="#" callbackName="helloworld">test</a>
...
<script>
function helloworld() { alert('hello world'); }
</script>
...
question ; how can i produce callBack to pass another function
<script>
...
var cbName = $('#aHw').attr('callbackName');
foo( passfunction ); //How???
...
</script>
<script>
function foo(callBack)
{
callBack(); // call hello world.
}
</script>
thanks in advance.
A function in JavaScript is just an object.
The question(s) don't make terribly much sense to me, but consider the following:
function fn1 () {
alert("fn1")
}
function doIt(cb) {
cb()
}
// direct -- to show point in general
// fn1 evaluates to the function-object, which is then passed
doIt(fn1)
// lookups up fn1 by name, then passes that function-object
// the value "fn1" can be any arbitrary string, such as that which
// was stored in the attr:
// e.g. doIt(window[cbName])
doIt(window["fn1"])
// anon function to iterate sameness
// the anon function returns a new function-object
doIt(function () { alert("anon") })
Happy coding.
Ok. So to have an anchor do something on MouseOver, you'd use this code:
<a id="aHw" href="#" onmouseover="doSomething()">test</a>
You can pass a function to another function this way:
function callSomeFunction( fn )
{
fn();
}
callSomeFunction( alert );
Or you can pass an anonymous function to the above:
callSomeFunction( function(){ alert( "Finally! A message!" ); } );
If you're trying to pass the name of a function as a string (which is a fundamentally bad idea and a terrible risk and hard to debug and DON'T DO IT), then you can use eval:
function callNamedFunction( fn )
{
eval(fn)()
}
Or you might be able to get away with:
function callNamedFunction( fn )
{
(window[fn])()
}
foo( Function('return ' + cbName)() )
I think that's what your after..
But if it's in the browser, and you know that the callback is a global object, you could do..
foo(window[cbName])
Well, if nothing else helps, eval() will:
function foo( callBack ) {
eval( callBack + '()' );
}
If you know where the function is defined (e.g window, or custom namespace) you can invoke it by the string name. Otherwise you would have to eval (bad idea). Also, use data-attributes.
test
...
// invoke runs a function by name in the provided context, or window
function invoke(cbname, context){
return (context || window)[cbname].call();
}
// get attribute through the data method
var funcName = $('a').data('callback');
// run callback
var result = invoke(funcName);

Resources