How to fix undefined is not an object (evaluating 'newOptions.merge')? - unit-testing

I am writing angular 2 app. This test is created by angular cli and fails by default:
it('should render title in a h1 tag', async(() => {
let fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('app works!');
}));
Error message is:
Failed: Error in ./AppComponent class AppComponent - inline template:51:14 caused by: undefined is not an object (evaluating 'newOptions.merge')
mergeOptions#webpack:///~/#angular/http/src/http.js:47:0 <- src/test.ts:69434:22
get#webpack:///~/#angular/http/src/http.js:147:0 <- src/test.ts:69534:110
As I can found newOptions.merge is called from mergeOptions method inside node_modules/#angular/http/src/http.js.
What I have to do in order to make tests passing?
Here is gist fro app.component.ts
Her is gist for app.component.html

You are using Angular 2 Http service, so you need to inject defaultOptions parameter, which is, in your case, empty.
Best scenario to make this work is to add the following lines in your spec.ts file:
beforeEach(() => {
TestBed.configureTestingModule({
providers: [{
provide: Http,
useFactory: (
backend: ConnectionBackend,
defaultOptions: BaseRequestOptions
) => {
return new Http(backend, defaultOptions);
},
deps: [BaseRequestOptions]
},
{
provide: BaseRequestOptions,
useClass: BaseRequestOptions
}
]
});
});
So, the line when you tell that provider BaseRequestOption uses class BaseRequestOptions is crucial.

Related

Unit test fails after adding a new test

I am currently working on an Ionic (v3) app and we have several tests to test services, pages and components.
Everything was working fine until I added a new test for a component.
Both tests run fine individually (if started with fdescribe, or if I comment one of them out).
The tests look like this:
verify-key.spec.ts
describe('Component: VerifyKey', () => {
let component: VerifyKeyComponent
let fixture: ComponentFixture<VerifyKeyComponent>
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [VerifyKeyComponent],
imports: [
IonicModule.forRoot(VerifyKeyComponent)
]
})
// create component and test fixture
fixture = TestBed.createComponent(VerifyKeyComponent)
// get test component from the fixture
component = fixture.componentInstance
})
...
})
wallet-select-coins.spec.ts
describe('Wallet-Select-Coin Component', () => {
let fixture: ComponentFixture<WalletSelectCoinsPage>
let component: WalletSelectCoinsPage
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [WalletSelectCoinsPage],
imports: [
IonicModule.forRoot(WalletSelectCoinsPage),
ComponentsModule,
IonicStorageModule.forRoot({
name: '__airgap_storage',
driverOrder: ['localstorage']
})
],
providers: [
SecretsProvider,
{
provide: SecureStorageService,
useFactory: SecureStorageFactory,
deps: [Platform]
},
{ provide: NavController, useClass: NavControllerMock },
{ provide: NavParams, useClass: NavParamsMock },
{ provide: StatusBar, useClass: StatusBarMock },
{ provide: SplashScreen, useClass: SplashScreenMock },
{ provide: Platform, useClass: PlatformMock }
]
})
}))
beforeEach(() => {
fixture = TestBed.createComponent(WalletSelectCoinsPage)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should not show hd-wallet dropdown if currency does not support it', () => {
let el = fixture.debugElement.nativeElement
let ethereumRadio = el.querySelector('#eth')
// click on ethereum
ethereumRadio.click()
fixture.detectChanges()
console.log(component.selectedProtocol)
expect(component.selectedProtocol).toBeDefined() // This fails
expect(component.selectedProtocol.identifier).toEqual('eth')
// eth should not show hd wallets
let hdWalletSelector = el.querySelector('#wallet-type-selector')
expect(hdWalletSelector).toBeFalsy()
})
})
If both tests are enabled, the second one fails at the line expect(component.selectedProtocol).toBeDefined() with the error Expected undefined to be defined.
If I comment out the line fixture = TestBed.createComponent(VerifyKeyComponent) from the first file, then the second test runs without any issues.
My first idea was that the TestBed somehow gets modified in the first test. So I tried adding TestBed.resetTestingModule() after the first test, but that didn't change anything.
Any help would be greatly appreciated.
When writing tests its best practice to clean up everything after every test so that all test can run in random order without effecting any other test.
I would suggest to introduce a afterEach() or afterAll() in every test which clears any data which is produced. Because most test environments run all tests in the same context.
And why would you like that your TestBed.configureTestingModule to be created async()? Your test will run but the async part will probably never get called before the it(..
Hope these ideas will help :)

How to add service to Angular2 test (CLI config, not as in docs)

I'm just now learning unit testing in general, but I'm simultaneously learning angular2. Angular CLI created my test scaffolds and I've configured the testing module with the neccessary component and directive. I'm trying to provide a stub service similar to the one decribed in the DOCS, but I'm getting the following error:
Error: Cannot create the component FoodListComponent as it was not imported
into the testing module
This is odd because the should create test passed when with the FoodListComponent before I added the service into the providers: []. My service works fine in the application, but I can provide it too if you need to see it.
Basically I'd like to know where in this testing set up I need to put the foodDataServiceStub, whether I'm adding the stub service to the TestBed object correctly, and where I need to inject the service via foodDataService = TestBed.get(FoodDataService);?
I'd be thankful for an explanation as well.
food-list.component.spec.ts
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { DebugElement, NO_ERRORS_SCHEMA } from '#angular/core';
import { TickerDirective } from '../../directives/ticker.directive';
import { FoodListComponent } from './food-list.component';
describe('FoodListComponent', () => {
let component: FoodListComponent;
let fixture: ComponentFixture<FoodListComponent>;
let foodDataServiceStub = {
name: 'test food name',
img: '',
description: 'test food description'
};
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FoodListComponent, TickerDirective ],
providers: [ { provide: FoodDataService, useValue: foodDataServiceStub } ], /* This line gives me the error */
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FoodListComponent);
component = fixture.componentInstance;
// foodDataService = TestBed.get(FoodDataService);
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

Error: No provider for MockBackend! when write unit test for angular2 final

I'm writting unit test for angular2 - karma - jasmine.
I used mockBackend to response data when service call API.
This is my code
function Connection(mockBackend) {
connection.mockRespond(new Response(
new ResponseOptions({
body: JSON.stringify(lookup)
}
)));
});
}
describe('Update Contact Details Test', () => {
let fixture, comp;
beforeEach(async(inject([MockBackend], (mockBackend: MockBackend) => {
Connection(mockBackend);
TestBed.configureTestingModule({
declarations: [ ContactFieldListComponent ],
providers: _.union(DEFAULT_PROVIDERS, [FieldService, LookupsService, ContactService]),
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: DEFAULT_IMPORT
})
.compileComponents().then(() => {
fixture = TestBed.createComponent(ContactFieldListComponent);
comp = fixture.componentInstance;
});
})));
it('get contact field list', () =>
expect(1).toBe(1);
});
});
It don't work with error Error: No provider for MockBackend!.
How can I resolve it?
It looks like you missed to add the MockBackend into the providers of the TestBed.
...
TestBed.configureTestingModule({
declarations: [ ContactFieldListComponent ],
providers: _.union(
DEFAULT_PROVIDERS, [
FieldService,
LookupsService,
ContactService,
MockBackend // <- add it here
]),
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: DEFAULT_IMPORT
})
...
Additionally I think you have to create another beforeEach under the one with the TestBed, because in the first one, MockBackend isn't registered.
I have a sample test for you, that perhaps can help you: https://github.com/angular-workshops/angular2-testing/blob/solution/tour-of-heroes/src/app/hero.service/hero.service.shallow.spec.ts

How to write an HTTP mock unit test in Angular 2 final release?

I have moved from RC4 to final release (2.1.0) and I am refactoring my unit tests to conform to 2.1.0 syntax. This is easy except for HTTP mocking.
I cannot find any examples for how to mock HTTP requests in 2.1.0
Here is an RC4 HTTP unit test. How would I re-write this in final release 2.1.0?
it('ngOnInit()',
async(inject([TestComponentBuilder, XHRBackend], (tcb:TestComponentBuilder, mockBackend:MockBackend) => {
tcb.createAsync(Route1ListComponent).then((fix:ComponentFixture<Route1ListComponent>) => {
// THIS BLOCK OF CODE I NEED HELP TO RE-WRITE TO 2.1.0
mockBackend.connections.subscribe(
(connection:MockConnection) => {
connection.mockRespond(new Response(
new ResponseOptions({
body: persons
}
)));
});
// THIS BLOCK OF CODE WILL NOT CHANGE
let instance = fix.componentInstance;
instance.ngOnInit();
expect(instance.persons.length).toBe(3);
});
})));
NOTE: Do NOT provide RC code please. Thanks
First thing you need to do is configure the TestBed. There's no more TestComponentBuilder. With the TestBed, it's like configuring an #NgModule from scratch, just for the test environment. This means you will add the component under test to the declarations, add any providers to the provider, and any imports to the imports.
To configure the mock backend for Http provider, you would just create the Http from the MockBackend.
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ HttpModule ],
declarations: [ RouteListComponent ],
providers: [
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (backend: MockBackend, options: BaseRequestOptions) => {
return new Http(backend, options);
},
deps: [ MockBackend, BaseRequestOptions ]
}
]
})
})
That should be it for the configuration, assuming you don't need any other providers or imports I don't know about.
For the test you'll first want to make it an async test, as you'll be doing asynchronous operations in the test. This hasn't changed from RC, you just use async. If the component uses templateUrl (and you're not using Webpack), then you will need to call TestBed.compileComponents(), otherwise no need to. After that you can create the component with TestBed.createComponent
let fixture: ComponentFixture<RouteListComponent>;
let component: RouteListComponent;
beforeEach(async(() => {
TestBed.configureTestingModule({ ... })
.compileComponents().then(() => {
fixture = TestBed.createComponent(RouteListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
}));
it('...', async(inject([MockBackend], (backend: MockBackend) => {
})))
Pretty much all the things above related to testing can be imported from #angular/core/testing. Your use of the MockBackend would still be the same.
Another note, you don't need to call the component.ngOnInit. That is called by the framework when you call fixture.detectChanges()
See Also:
Testing documentation for more complete examination of testing support.
Many thanks to #peeskillet for helping me reach my answer ..
import {APP_BASE_HREF} from '#angular/common';
import {async, ComponentFixture, TestBed} from '#angular/core/testing';
import {By} from '#angular/platform-browser';
import {AppModule} from '../../../app.module';
import {persons} from '../../../data/persons';
import {Route1ListComponent} from './route1-list.component';
// HTTP mocking imports
import {BaseRequestOptions, Http, Response, ResponseOptions} from '#angular/http';
import {MockBackend, MockConnection} from '#angular/http/testing';
describe('route1-list.component.ts', () => {
let fix: ComponentFixture<Route1ListComponent>;
let instance: Route1ListComponent;
let injector: any;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [AppModule],
providers: [{provide: APP_BASE_HREF, useValue: '/'},
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (pBackend: MockBackend, pOptions: BaseRequestOptions) => {
return new Http(pBackend, pOptions);
},
deps: [MockBackend, BaseRequestOptions]
}]
}).compileComponents()
.then(() => {
fix = TestBed.createComponent(Route1ListComponent);
instance = fix.componentInstance;
injector = fix.debugElement.injector;
});
}));
it('should instantiate component', () => {
expect(instance).toEqual(jasmine.any(Route1ListComponent));
});
it('should have expected text', () => {
let el = fix.debugElement.query(By.css('section.route1-list')).nativeElement;
expect(el.textContent).toMatch(/route 1 list view/i, 'should have expected text');
});
it('ngOnInit()', async(() => {
let backend = injector.get(MockBackend);
backend.connections.subscribe(
(connection: MockConnection) => {
connection.mockRespond(new Response(
new ResponseOptions({
body: persons
}
)));
});
fix.detectChanges(); // Calls instance.ngOnInit()
expect(instance.persons.length).toBe(3);
}));
it('ngOnInit() failure', async(() => {
let backend = injector.get(MockBackend);
backend.connections.subscribe(
(connection: MockConnection) => {
connection.mockError(new Error('error'));
});
fix.detectChanges(); // Calls instance.ngOnInit()
expect(instance.persons).toBeUndefined();
}));
});
Notice that, at the time of writing, the Angular2 docs at ..
https://angular.io/docs/ts/latest/api/http/testing/index/MockBackend-class.html
https://angular.io/docs/ts/latest/api/http/testing/index/MockConnection-class.html
seem to be wrong.
When I use Injector.resolveAndCreate as detailed in the docs I get the error:
Property 'resolveAndCreate' does not exist on type 'typeof Injector'.
To fix it I had to base my answer on the answer provided by #peeskillet

angular 2 rc7 - testing service - Error: No provider for String

I've getting an error in angular 2 testing using the webpack quickstart project.
Error: No provider for String! in config/karma-test-shim.js
I've never seen this error where the provider for String is missing. I figured out its related the private url:string in the services constructor but how do I resolve it?
Here's my testfile
describe('http.ts',()=>{
beforeEach(()=>{
TestBed.configureTestingModule({
providers:[
SbHttp,
MockBackend,
BaseRequestOptions,
{ provide: Http, useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
}, deps: [MockBackend, BaseRequestOptions]}
],
imports: [
HttpModule
]
})
});
afterEach(()=>{
TestBed.resetTestingModule()
});
it('get() should work', inject([SbHttp],(sbHttp:SbHttp)=>{
expect(true).toBeTruthy()
}))
})
and here's the SbHttp service
#Injectable()
export class SbHttp{
private baseUrl:string;
constructor( private url:string, private headers:Headers, private http:Http
){
this.baseUrl = utils.stripTrailingSlash(url)
}
}
If I change to private url:any I'm getting this error
Can't resolve all parameters for SbHttp: (?, Headers, Http).
You need to make it injectable by creating a token, using #Inject() to inject with the token, and then add it as a provider in the providers list .
import { OpaqueToken, Injct } from '#angular/core';
const APP_URL = new OpaqueToken('app.url');
class SbHttp {
constructor(#Inject(APP_URL) url: string, ...) {}
}
TestBed.configureTestingModule({
providers: [
{ provide: APP_URL, useValue: 'http://localhost' }
]
});
You'll also need to configure the providers in the real app also. As for the Headers, I'm not sure, but I think you might get the error also once you fix this one. I'm not sure where you expect to be getting that from.
I highly suggest you take a look at the dependency injection chapter from the Angular documentation if you are new to DI with Angular.