Angular 2 TestBed, Mocking Methods without Dependency Injection - unit-testing

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.

Related

Karma unit testing - finding out which abstract class a component implements

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;
} );
} );

Angular2 - Call function from a tested component

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.

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!

How to mock imported named function in Jest when module is unmocked

I have the following module I'm trying to test in Jest:
// myModule.js
export function otherFn() {
console.log('do something');
}
export function testFn() {
otherFn();
// do other things
}
As shown above, it exports some named functions and importantly testFn uses otherFn.
In Jest when I'm writing my unit test for testFn, I want to mock the otherFn function because I don't want errors in otherFn to affect my unit test for testFn. My issue is that I'm not sure the best way to do that:
// myModule.test.js
jest.unmock('myModule');
import { testFn, otherFn } from 'myModule';
describe('test category', () => {
it('tests something about testFn', () => {
// I want to mock "otherFn" here but can't reassign
// a.k.a. can't do otherFn = jest.fn()
});
});
Any help/insight is appreciated.
Use jest.requireActual() inside jest.mock()
jest.requireActual(moduleName)
Returns the actual module instead of a mock, bypassing all checks on whether the module should receive a mock implementation or not.
Example
I prefer this concise usage where you require and spread within the returned object:
// myModule.test.js
import { otherFn } from './myModule.js'
jest.mock('./myModule.js', () => ({
...(jest.requireActual('./myModule.js')),
otherFn: jest.fn()
}))
describe('test category', () => {
it('tests something about otherFn', () => {
otherFn.mockReturnValue('foo')
expect(otherFn()).toBe('foo')
})
})
This method is also referenced in Jest's Manual Mocks documentation (near the end of Examples):
To ensure that a manual mock and its real implementation stay in sync, it might be useful to require the real module using jest.requireActual(moduleName) in your manual mock and amending it with mock functions before exporting it.
Looks like I'm late to this party, but yes, this is possible.
testFn just needs to call otherFn using the module.
If testFn uses the module to call otherFn then the module export for otherFn can be mocked and testFn will call the mock.
Here is a working example:
myModule.js
import * as myModule from './myModule'; // import myModule into itself
export function otherFn() {
return 'original value';
}
export function testFn() {
const result = myModule.otherFn(); // call otherFn using the module
// do other things
return result;
}
myModule.test.js
import * as myModule from './myModule';
describe('test category', () => {
it('tests something about testFn', () => {
const mock = jest.spyOn(myModule, 'otherFn'); // spy on otherFn
mock.mockReturnValue('mocked value'); // mock the return value
expect(myModule.testFn()).toBe('mocked value'); // SUCCESS
mock.mockRestore(); // restore otherFn
});
});
import m from '../myModule';
Does not works for me, I did use:
import * as m from '../myModule';
m.otherFn = jest.fn();
I know this was asked a long time ago, but I just ran into this very situation and finally found a solution that would work. So I thought I'd share here.
For the module:
// myModule.js
export function otherFn() {
console.log('do something');
}
export function testFn() {
otherFn();
// do other things
}
You can change to the following:
// myModule.js
export const otherFn = () => {
console.log('do something');
}
export const testFn = () => {
otherFn();
// do other things
}
exporting them as a constants instead of functions. I believe the issue has to do with hoisting in JavaScript and using const prevents that behaviour.
Then in your test you can have something like the following:
import * as myModule from 'myModule';
describe('...', () => {
jest.spyOn(myModule, 'otherFn').mockReturnValue('what ever you want to return');
// or
myModule.otherFn = jest.fn(() => {
// your mock implementation
});
});
Your mocks should now work as you would normally expect.
The transpiled code will not allow babel to retrieve the binding that otherFn() is referring to. If you use a function expession, you should be able to achieve mocking otherFn().
// myModule.js
exports.otherFn = () => {
console.log('do something');
}
exports.testFn = () => {
exports.otherFn();
// do other things
}
// myModule.test.js
import m from '../myModule';
m.otherFn = jest.fn();
But as #kentcdodds mentioned in the previous comment, you probably would not want to mock otherFn(). Rather, just write a new spec for otherFn() and mock any necessary calls it is making.
So for example, if otherFn() is making an http request...
// myModule.js
exports.otherFn = () => {
http.get('http://some-api.com', (res) => {
// handle stuff
});
};
Here, you would want to mock http.get and update your assertions based on your mocked implementations.
// myModule.test.js
jest.mock('http', () => ({
get: jest.fn(() => {
console.log('test');
}),
}));
Basing on Brian Adams' answer this is how I was able to use the same approach in TypeScript. Moreover, using jest.doMock() it's possible to mock module functions only in some specific tests of a test file and provide an individual mock implementations for each of them.
src/module.ts
import * as module from './module';
function foo(): string {
return `foo${module.bar()}`;
}
function bar(): string {
return 'bar';
}
export { foo, bar };
test/module.test.ts
import { mockModulePartially } from './helpers';
import * as module from '../src/module';
const { foo } = module;
describe('test suite', () => {
beforeEach(function() {
jest.resetModules();
});
it('do not mock bar 1', async() => {
expect(foo()).toEqual('foobar');
});
it('mock bar', async() => {
mockModulePartially('../src/module', () => ({
bar: jest.fn().mockImplementation(() => 'BAR')
}));
const module = await import('../src/module');
const { foo } = module;
expect(foo()).toEqual('fooBAR');
});
it('do not mock bar 2', async() => {
expect(foo()).toEqual('foobar');
});
});
test/helpers.ts
export function mockModulePartially(
modulePath: string,
mocksCreator: (originalModule: any) => Record<string, any>
): void {
const testRelativePath = path.relative(path.dirname(expect.getState().testPath), __dirname);
const fixedModulePath = path.relative(testRelativePath, modulePath);
jest.doMock(fixedModulePath, () => {
const originalModule = jest.requireActual(fixedModulePath);
return { ...originalModule, ...mocksCreator(originalModule) };
});
}
Mocking functions of a module is moved to helper function mockModulePartially located in a separate file so it can be used from different test files (which, in common, can be located in other directories). It relies on expect.getState().testPath to fix path to a module (modulePath) being mocked (make it relative to helpers.ts containing mockModulePartially). mocksCreator function passed as a second argument to mockModulePartially should return mocks of the module. This function receives originalModule and mock implementations can optionally rely on it.
I solved my problem with a mix of the answers that I found here:
myModule.js
import * as myModule from './myModule'; // import myModule into itself
export function otherFn() {
return 'original value';
}
export function testFn() {
const result = myModule.otherFn(); // call otherFn using the module
// do other things
return result;
}
myModule.test.js
import * as myModule from './myModule';
describe('test category', () => {
let otherFnOrig;
beforeAll(() => {
otherFnOrig = myModule.otherFn;
myModule.otherFn = jest.fn();
});
afterAll(() => {
myModule.otherFn = otherFnOrig;
});
it('tests something about testFn', () => {
// using mock to make the tests
});
});
On top of the first answer here, you can use babel-plugin-rewire to mock imported named function too. You can check out the section superficially for
named function rewiring.
One of the immediate benefits for your situation here is that you do not need to change how you call the other function from your function.

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 ...
})
});