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!
Related
I know this is kind of a weird question, but I would like to mock an inner require(); statement in a constructor. This is my test file:
import { PiControl } from "../pi";
describe("PiControl", () => {
beforeEach(() => {
this.piControl = new PiControl();
});
it("Should initialise the control and call the board ready event.", () => {
expect(true).toBe(true);
});
});
As you can see below, I'm instantiating the class PiControl and when I run my test and make an instance of the PiControl.
export class PiControl extends BaseControl {
public board: any;
constructor() {
super();
// No idea if this can be mocked, but I would like to.
const Raspi = require("raspi-io");
const five = require("johnny-five");
const board = new five.Board({
io: new Raspi({ enableSoftPwm: true }),
});
this.board = board;
}
public loadControl(): void {
this.board.on("ready", this.setupMotors);
}
}
As you can see below, that is what the test outputs. It will call the original library (which is supposed to run on a Raspberry PI) and tries to run it on my development machine.
Question: is there a way to workaround this issue?
For what it's worth, I'm testing the whole application using Jest (almost out-of-the-box config).
I'm quite new to AngularJS and Karma. Nevertheless I'm ordered to write meaningful unit tests. As we are asked to implement the classes OnInit and OnDestroy in each component we create, I wonder whether there is a way to check with Karma if a component actually implements them.
Has anybody got an idea?
In order to check for implementation, you will need to check if the component actually implements the methods that are required by these abstract classes.
If DummyComponent is a component which implements OnInit, OnDestroy with no other dependencies then:
describe( 'DummyComponent', () => {
let component: DummyComponent;
beforeEach( () => {
TestBed.configureTestingModule( {
declarations: [
DummyComponent
],
providers: [
{ provide: ComponentFixtureAutoDetect, useValue: true }
]
} );
component = TestBed.createComponent( DummyComponent ).componentInstance;
} );
it( 'should implement onInit and onDestroy', () => {
expect( component.ngOnInit ).toBeDefined;
expect( component.ngOnDestroy ).toBeDefined;
} );
} );
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.
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.
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