Angular2 - Call function from a tested component - unit-testing

I'm currently writing unit tests for Angular2 with Karma and Jasmine, but I'm pretty new in the unit testing and I'm facing some difficulties. When it comes to testing hardcoded properties or properties that don't involve async functions, it's all okay, but I need to be able to call the component's functions in order for some variables to get their values. What I'm doing is the following:
My component:
export class LoginComponent implements OnInit {
formLoginId: string;
loginUrl: string;
email: string;
password: string;
constructor(private googleAuthService: GoogleAuthService,
private authService: AuthenticationService,
private validationService: ValidationService,
private router: Router,
private titleService: Title) {
this.titleService.setTitle("Login");
this.formLoginId = "#form-login";
}
ngOnInit() {
this.googleAuthService.getLink((response) => {
this.loginUrl= response.json().url;
});
}
login() {
if (this.validationService.isValid(this.formLoginId)) {
this.authService.login(this.email, this.password);
}
}
Now I want to write a unit test which can check if the loginUrl has taken any value. My test is bellow:
describe('Login Component', ()=> {
let component:LoginComponent;
let fixture:any;
beforeEach(async(()=> {
TestBed.configureTestingModule({
//declarations,imports and providers
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
}); /some-other-tests/
it('should have login url', fakeAsync(()=> {
component.ngOnInit();
tick(1000);
expect(component.loginUrl).toBeDefined();
}));
});
But it seems that its not working. I'm still getting undefined for the mentioned variable. How can I call a method from a component and check the variables after its result?
Thanks!

In this case, you need to mock the GoogleAuthService to return with some information, as otherwise that getLink never resolves.
You can specify a mock provider for the GoogleAuthService and have it return an observable that's already resolved.

Related

How to unit test an ember controller

I have a single action defined in an ember controller that calls 2 separate functions that are part of the controller. I'd like to mock out those functions in a unit test in order to confirm if the action method called the correct function.
My controller looks like this:
export default Ember.Controller.extend({
functionA() {
return;
},
functionB() {
return;
},
actions: {
actionMethod(param) {
if(param) {
return this.functionA();
}
else {
return this.functionB();
}
}
}
});
In practice, the controller works, however in the unit test, functionA and functionB are both undefined. I tried to log this to the console, but can't find where functionA and functionB are, so I'm unable to properly mock them. I expected them to be in the top level of the object next to actions, but instead I only found _actions with actionMethod properly defined.
My unit test looks like below
const functionA = function() { return; }
const functionB = function() { return; }
test('it can do something', function(assert) {
let controller = this.subject();
// I don't want the real functions to run
controller.set('functionA', functionA);
controller.set('functionB', functionB);
controller.send('actionMethod', '');
// raises TypeError: this.functionA is not a function
// this doesn't work etiher
// controller.functionB = functionB;
// controller.functionA = functionA;
// controller.actions.actionMethod();
}
Does anyone have any ideas on how I can replace those functions in the testing environment? Or perhaps, is there a better way to test this functionality or set up my controller?
edit
typo: this.subject to this.subject()
To replace the functions of the controller in the unit test, you can pass parameter to the this.subject() function:
let controller = this.subject({
functionA(){
//this function overriddes functionA
},
functionB(){
//this function overriddes functionB
},
});
Look at the sample twiddle.
This method is especially useful for replacing the injected service of the controllers.
Introduce corresponding property you are dealing with, let us say name property,
So your controllers would be looking like this,
import Ember from 'ember';
export default Ember.Controller.extend({
name:'',
functionA() {
this.set('name','A');
},
functionB() {
this.set('name','B');
},
actions: {
actionMethod(param) {
if(param) {
return this.functionA();
}
else {
return this.functionB();
}
}
}
});
And you can test for the name property value after calling actionMethod.
test(" testing functionA has been called or not", function(assert){
let controller = this.subject();
controller.send('actionMethod',true);
//If you would like to call functionA just say controller.functionA()
assert.strictEqual(controller.get('name'),'A',' name property has A if actionMethod arguments true');
controller.send('actionMethod',false);
assert.strictEqual(controller.get('name'),'B',' name property has B actionMethod arguments false');
});

Mocking ngrx/store

This is in regards to the Angular 2 official release. I know that unit testing has changed drastically between beta, RC, and the official release.
What's a good way to mock #ngrx/store in a unit test when it's used as a parameter in a constructor? It's not as simple as mocking a service.
For example, if I wanted to mock a service, then I could do something like this:
let serviceStub = { }; // not a true mocked service, just a stub, right?
let de: DebugElement;
let el: HTMLElement;
let nativeEl: Element;
let comp: Typeahead;
let fixture: ComponentFixture<Typeahead>;
describe('Component:Typeahead', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [...],
declarations: [Typeahead],
providers: [
{provide: TypeaheadService, useValue: serviceStub} // provides the service that is being "mocked"
]
}).compileComponents();
fixture = TestBed.createComponent(Typeahead);
nativeEl = fixture.nativeElement;
comp = fixture.componentInstance;
de = fixture.debugElement;
});
});
And this works.
For ngrx/store, however, it does not (if you substitute Store in for TypeaheadService). I'm thinking you have to write a mock class that extends Store, and then provide that to the component that is being tested, but I'm not sure why that is the case (if that is even the case).
I'm just confused as how to mock ngrx/store in my unit tests and couldn't find any documentation on their site or github. Maybe I overlooked it.
Thank you for posting the question and suggesting a potential solution!
The way I've mocked it is, to use the actual actions to set an initial state i.e. a mocked state before each test. Here's an example
beforeEach(inject([Store], (store: Store<ApplicationState>) => {
const someFakeState = {
counter: 9,
counterFilter: 'A_FAKE_COUNTER_FILTER'
};
store.dispatch(new myActionToSetSomeData(someFakeState));
}));
Inside your it() block you should now be able to check that the component is displaying a count of 9 and a filtering by 'A_FAKE_COUNTER_FILTER'.
You can of course set the state inside your it block, rather than beforeEach, as long as its before the component is instantiated.
You can use forRoot (>= v4) or provideStore ( <= v3) to provide the data to the StoreModule and the rest is done for you:
1 - Import it:
import { StoreModule } from '#ngrx/store';
2 - Create a mock data:
/*
* Mock data
*/
const PAINTS = [];
3 - Import it in you test:
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ StoreModule.forRoot(PAINTS) ]
})
}))
In previous versions (before v4), you should use provideStore(PAINTS) instead of forRoot(PAINTS). See the the changelog here
Yes, you do have to mock ngrx/store, but not only Store. Store expects three arguments; one of type Observable, and two of type Observer, which is an interface. So, I tried two things. Passing in null values to the StoreMock super() constructor, but that failed at my assertion. My other solution was to implement the Observer interface with a mock class (Observable in this case). This way I could pass defined values into the super StoreMock constructor.
This is just an illustrative example. The ObservableMock doesn't actually mock any functionality that I'm trying to test in my application. It's serving as an enabler so that Store can be injected as a provider into the Component I'm trying to test.
Since Observer is an interface, you have to implement its function declarations in the mock: next, error, and complete.
class ObservableMock implements Observer<any> {
closed?: boolean = false; // inherited from Observer
nextVal: any = ''; // variable I made up
constructor() {}
next = (value: any): void => { this.nextVal = value; };
error = (err: any): void => { console.error(err); };
complete = (): void => { this.closed = true; }
}
let actionReducer$: ObservableMock = new ObservableMock();
let action$: ObservableMock = new ObservableMock();
let obs$: Observable<any> = new Observable<any>();
class StoreMock extends Store<any> {
constructor() {
super(action$, actionReducer$, obs$);
}
}
And now you can add Store as a provider in your Component's test module.
describe('Component:Typeahead', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [...],
declarations: [Typeahead],
providers: [
{provide: Store, useClass: StoreMock} // NOTICE useClass instead of useValue
]
}).compileComponents();
});
});
I am sure there are other ways to do it. So if anyone has any other answers, please post them!

Angular 2 TestBed, Mocking Methods without Dependency Injection

Using TestBed, we are able to create mock classes for classes that are available with dependency injection. For example, MyButtonClass has access to ElementRef and MyService since they are implemented with dependency injection, and so we can override them. The problem I have is that, to write a Jasmine test, I have to create mock classes to override methods of classes that are not accessed with dependency injection.
In this case, ScriptLoader.load will load ThirdPartyCheckout in the global space. This means, it might not be available when Jasmine reads what is inside the subscribe operator. For this reason, I would like to mock the former first and then the latter after. Or maybe there is a different way to get around this.
It would be great if someone can suggest a way to create mock classes to override the ScriptLoader.load method and ThirdPartyCheckout.configure method.
The directive to be tested:
#Directive({
selector: '[myButton]'
})
export class MyButtonClass implements AfterViewInit {
private myKey: string;
constructor(private _el: ElementRef, private myService: MyService) {}
ngAfterViewInit() {
this.myService.getKey()
.then((myKey: string) => {
this.myKey = myKey;
ScriptLoader.load('https://thirdpartyurl.js', this._el.nativeElement)
.subscribe(
data => {
this.handeler = ThirdPartyCheckout.configure(<any>{
key: this.myKey
// etc
// and some methods go here
});
},
error => {
console.log(error);
}
);
});
}
}
Here is the test code:
#Component({
selector: 'test-cmp',
template: ''
})
class TestComponent {}
class mockMyService {
getKey() {
return Promise.resolve('this is a key in real code');
}
}
describe('myButton', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TestComponent, MyButtonClass],
providers: [
{provide: MyService, useClass: mockMyService}
]
});
});
describe('ngAfterViewInit', fakeAsync(() => {
const template = '<div><div myButton></div></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}});
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
tick();
}));
});
Functions being first-class citizens, you can just assign a new function to it
let originalFn;
beforeEach(() => {
originalFn = ScriptLoader.load;
});
afterEach(() => {
ScriptLoader.load = originalFn;
});
it('...', fakeAsync(() => {
ScriptLoader.load = (url, el: Element): Observable<string> => {
return Observable.of('HelloSquirrel');
};
...
}));
Other than this, you might want to just consider using DI. One of the main reasons for using DI is for better testability. For the ScriptLoader just make the method a non static method, and for the third party lib just create as abstraction service layer for it.

error: No provider for #Attribute('sampleString')

We are trying to write unit test for a component which uses a third party java script library. The constructor of our component looks like -
#Constructor(#Inject(ElementRef) private eleref:ElementRef, #Attribute('sampleString') private sampleString: string)
We use that attribute to pass it to my third party library. And in there it does a specific task based on that attribute. If I don't pass it, it means simply ignore it and do regular stuff.
When we try to use/inject this component in our test class, it gives us error.
Error: DI Exception: No Provider for #Attribute('sampleString')!
Can someone suggest for what would be the provider for this? If your example can elaborate why this error and how to solve such issues in general, that will be bonus.
//component
#component({selector: 'mytable', templateUrl:'URL of template'}
export class mycomp{
//data members
constructor (element ref injection, #Attribute('sample string') private sampleString:string){}
//public methods
private ngOninit(){ this.dataview = dataview of third party lib. }
}
//Test
Describe("my test",()=>{
beforeEachProviders(()=>[ mycomp, provider for elementRef]);
It('test', async(inject ([TestComponentBuilder,mycomp], (tcb: TestComponentBuilder) => {
tcb.createAsync(mycomp)
.then ((fixture)=> {
expect(true). toBe(false)
})
});
The attribute needs to be
#Attribute('sampleString')
instead of
#Attribute('sampleString')
You need a test component that wraps the component that you actually want to test to be able to pass the attribute:
#component({
selector: 'mytable',
templateUrl:'URL of template'
}
export class mycomp{
//data members
constructor (element ref injection, #Attribute('sampleString') private sampleString:string){}
//public methods
private ngOninit(){ this.dataview = dataview of third party lib. }
}
#component({
selector: 'test',
template:'<mytable sampleString="xxx"></mytable>'
}
export class TestComponent{
}
//Test
describe("my test",()=>{
beforeEachProviders(()=>[ mycomp, provider for elementRef]);
it('test', async(inject ([TestComponentBuilder,mycomp], (tcb: TestComponentBuilder) => {
tcb.createAsync(TestComponent)
.then ((fixture)=> {
expect(true). toBe(false)
// get the mycomp component from fixture ...
})
});

spyOn method in constructor with jasmine

I want to spyOn a promise and fake that promise in my unit test but the problem is that if I run first the contructor that the problem that he first run the promise and then run the Spyon.
But when i first run the spyOn and then the constructor it gives a error that storage is undefined.
Does someone know how to fix this?
Spec file:
describe('Settings Service', () => {
beforeEach(() => {
settingsService = new SettingsService(); // This gives a error beceause it runs the promise
spyOn(settingsService.storage, 'get').and.callFake((key: String): Promise<string> => {
return new Promise((resolve, reject) => { resolve('url'); });
});
});
constructor:
constructor() {
this.storage = new Storage(LocalStorage);
this.storage.get('url').then(data => {
this.setLink(data);
});
}
UPDATE:
I tried also this:
let injector: any = ReflectiveInjector.resolveAndCreate([SettingsService]);
settingsService = injector.get(SettingsService);
spyOn(settingsService.storage, 'get').and.callFake((key: String): Promise<string> => {
return new Promise((resolve, reject) => { resolve('https://secure.info/pascal'); });
});
The problem you have is that you are instantiating Storage within the constructor, so you have no access to it from the outside. That means that you cannot mock it.
Setting spyOn before calling settingsService = new SettingsService(); doesn't work either because the field storage has not been created yet.
You have two ways to solve this:
Mocking the service $httpBackend using the following code. Take a look at this post as an example
beforeEach(inject(function($injector) {
service = $injector.get('carService');
$httpBackend = $injector.get('$httpBackend');
$httpBackend.when('GET', "/api/cars/types").respond(["Toyota", "Honda", "Tesla"]);
}));
This way you can mock the promise you get when calling this.storage.get('url') and test the behaviour.
Making Storage a service and injecting it mocked: If you use this approach you could moke Storage and therefore mock the behaviour of this.storage.get('url'). The code of your class `` would look like this
static $inject = ['Storage'];
constructor(storage: Storage) {
this.storage = storage;
this.storage.get('url').then(data => {
this.setLink(data);
});
}
But this way depends on how do you define and use Storage so generally the first way will be better