#Input()
public set isRunning(value: boolean) {
if (!value) {
this.cancelTimeout();
this.isDelayedRunning = false;
return;
}
if (this.currentTimeout) {
return;
}
this.currentTimeout = setTimeout(() => {
this.isDelayedRunning = value;
this.cancelTimeout();
}, this.delay);
}
The code above is an #Input for an angular 2 component. I have a problem in creating a test case for the input as I do not know how to create a test for this kind of input. Should I create a getter? How do I do this? I cannot find any reference for this.
With a setter (set), all you do is assign the value to the property (method)
let fixture = TestBed.createComponent(TestComponent);
let component = fixture.componentInstance;l
component.isRunning = true;
fixture.detectChanges();
For the timeout, you might need to do something like
import { fakeAsync } from '#angular/core/testing;
it('should change isDelayedRunning', fakeAsync(() => {
let fixture = TestBed.createComponent(TestComponent);
let component = fixture.componentInstance;
fixture.detectChanges();
component.isRunning = true;
// wait for timeout
tick(200);
fixture.detectChanges();
expect(fixture.componentInstance.isDelayedRunning).toBe(true);
}));
fakeAsync won't work if you are using templateUrl in your component. So you have to use async. But AFAIK, there's no facility like tick where we can control the wait period, so you might have to just set a timeout in the test
import { async } from '#angular/core/testing';
it('should change isDelayedRunning', async(() => {
let fixture = TestBed.createComponent(TestComponent);
let component = fixture.componentInstance;
fixture.detectChanges();
component.isRunning = true;
setTimeout(() => {
fixture.detectChanges();
expect(fixture.componentInstance.isDelayedRunning).toBe(true);
}, 200);
}));
Related
Given a simple component that subscribes to the activated route query params in ngOnInit:
export class FooComponent implements OnInit {
private queryParams: any;
constructor(
private activatedRoute: ActivatedRoute
) { }
ngOnInit() {
this.activatedRoute.queryParams.subscribe(params => this.queryParams = params);
}
active(foo: number): boolean {
return this.queryParams['foo'] &&
foo === +this.queryParams['foo'];
}
}
The active function should return true when the foo query param is present and its value matches the supplied parameter.
In the accompanying unit tests for this component, I want to change the value of the query params within each it block to test the query param not being present, matching the parameter and not matching the parameter.
describe('FooComponent', () => {
let component: FooComponent;
let fixture: ComponentFixture<FooComponent>;
let activatedRoute: ActivatedRoute;
class MockActivatedRoute {
queryParams = Observable.of({});
}
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [FooComponent],
providers: [
{ provide: ActivatedRoute, useClass: MockActivatedRoute }
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FooComponent);
component = fixture.componentInstance;
fixture.detectChanges();
activatedRoute = fixture.debugElement.injector.get(ActivatedRoute);
});
describe('active', () => {
it('should return false if the foo query param is not present', () => {
activatedRoute.queryParams = Observable.of({});
let result = component.active(100);
expect(result).toBe(false);
});
it('should return false if the foo query param does not match the supplied parameter', () => {
activatedRoute.queryParams = Observable.of({ foo: '500' });
let result = component.active(100);
expect(result).toBe(false);
});
it('should return true if the foo query param does not match the supplied parameter', () => {
activatedRoute.queryParams = Observable.of({ foo: '500' });
let result = component.active(500);
expect(result).toBe(true);
});
});
});
Rather the value of the private queryParams member of the FooComponent class does not update within each it block. I've tried the various methods of async, fixture.whenStable(), and fakeAsync/tick.
How do I update the value of the subscription for each unit test?
It's because you are assigning a new Observable, but the client is already subscribed to the first Observable. This happens because ngOnInit is called when you first call fixture.detectChanges(). If you waited to called fixture.detectChanges() after you assign the new Observable to the queryParams, then that Observable would be used.
Another option (maybe preferred) is to instead of using an Observable, you can use a Subject. With this, you can control when data is emitted, and what to emit.
import { Subject } from 'rxjs/Subject'
import { fakeAsync, tick } from
class MockActivatedRoute {
queryParams = new Subject<any>();
}
let route: MockActivatedRoute;
beforeEach(() => {
/* configure */
route = <MockActivatedRoute>TestBed.get(ActivatedRoute);
})
it('', fakeAsync(() => {
route.queryParams.next(newparams); // emit something
tick(); // wait for resolution
fixture.detectChanges(); // detect changes (for ui)
expect(...)
}))
I say this options might be preferred as it allows for emitting multiple values in the same test.
I have a sample code for checkbox written with Angular2.
<div class="col-sm-7 align-left" *ngIf="Some-Condtion">
<input type="checkbox" id="mob_Q1" value="Q1" />
<label for="mob_Q1">Express</label>
</div>
I want to unit test the above checkbox. Like I want to recognize the checkbox and test whether it is check-able. How do I unit test this with Karma Jasmine?
Component, e.g. CheckboxComponent, contains input element. Unit test should looks like:
import {ComponentFixture, TestBed} from '#angular/core/testing';
import {By} from '#angular/platform-browser';
import {CheckboxComponent} from './checkbox.component';
describe('Checkbox test.', () => {
let comp: CheckboxComponent;
let fixture: ComponentFixture<CheckboxComponent>;
let input: Element;
beforeEach(() => {
TestBed.configureTestingModule(
{
declarations: [CheckboxComponent],
},
);
fixture = TestBed.createComponent(CheckboxComponent);
comp = fixture.componentInstance;
input = fixture.debugElement.query(By.css('#mob_Q1')).nativeElement;
});
it('should click change value', () => {
expect(input.checked).toBeFalsy(); // default state
input.click();
fixture.detectChanges();
expect(input.checked).toBeTruthy(); // state after click
});
});
IS there a need to write fixture.detectChanges()?
I went through the same test without this and it ends with success.
Button 1 is 'checked' by default
const button1 = debugElement.nativeElement.querySelector(selectorBtn1);
const button2 = debugElement.nativeElement.querySelector(selectorBtn2);
...
expect(button1.checked).toBeTruthy();
expect(button2.checked).toBeFalsy();
button2.click();
expect(button1.checked).toBeFalsy();
expect(button2.checked).toBeTruthy();
...
ngModel directive is async one and requires to use asynchronous capabilities of Angular unit testing. Adding async and whenStable functions.
it('checkbox is checked if value is true', async(() => {
component.model = true;
fixture.detectChanges();
fixture.whenStable().then(() => {
const inEl = fixture.debugElement.query(By.css('#mob_Q1'));
expect(inEl.nativeElement.checked).toBe(true);
});
}));
Source LinkLink
I'm trying to test a component, which uses a service that makes async http calls. The service returns an Observable, which the component subscribes on.
Service code snippet:
getRecentMachineTemperatures(_machine_Id): Observable<IDeviceReadings[]> {
return this.http.get(TemperatureService.URL + _machine_Id)
.map(response => { return response.json(); })
.map((records: Array<any>) => {
let result = new Array<IDeviceReadings>();
if (records) {
records.forEach((record) => {
let device = new IDeviceReadings();
device.device_id = record.device_id;
if (record.d) {
record.d.forEach((t) => {
let temperature = new ITemperature();
temperature.timestamp = t.timestamp;
temperature.value = t.temperature;
device.temperatures.push(temperature);
});
}
result.push(device);
});
}
return result;
});
}
Component code snippet:
ngOnInit() {
this.getRecentTemperatures();
}
getRecentTemperatures() {
this.temperatureService.getRecentMachineTemperatures(this.machine_id)
.subscribe(
res => {
let device1 = res[0];
this.deviceId = device1.device_id;
this.initTemperatures(device1.temperatures);
this.updateChart();
},
error => console.log(error));
}
My Test sets up dependencies, spies on the service 'getRecentMachineTemperatures' and sets i to return some stub data. I've been googling around for ways to test this, thus resulting in 3 different test, trying to test the same thing. Each giving me a different error.
temperature.component.spec.ts:
let machine_id = 1;
let comp: TemperatureComponent;
let fixture: ComponentFixture<TemperatureComponent>;
let de: DebugElement;
let el: HTMLElement;
let temperatureService: TemperatureService;
let stubDevices: IDeviceReadings[];
let stubTemperatures: ITemperature[];
let spyRecentTemps: Function;
describe('Component: Temperature', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TemperatureComponent],
imports: [ ChartsModule ],
providers: [
MockBackend,
BaseRequestOptions,
{ provide: Http,
useFactory: (backend, defaultOptions) => {
return new Http(backend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]},
TemperatureService
]
});
stubDevices = new Array<IDeviceReadings>();
let stubDevice = new IDeviceReadings();
stubDevice.device_id = 'stub device';
stubDevice.temperatures = new Array<ITemperature>();
let stubTemp = new ITemperature();
stubTemp.timestamp = new Date().getTime();
stubTemp.value = 10;
stubDevice.temperatures.push(stubTemp);
stubDevices.push(stubDevice);
stubTemperatures = new Array<ITemperature>();
let stubTemp2 = new ITemperature();
stubTemp.timestamp = new Date().getTime() + 1;
stubTemp.value = 11;
stubTemperatures.push(stubTemp2);
fixture = TestBed.createComponent(TemperatureComponent);
comp = fixture.componentInstance;
temperatureService = fixture.debugElement.injector.get(TemperatureService);
spyRecentTemps = spyOn(temperatureService, 'getRecentMachineTemperatures')
.and.returnValue(Observable.of(stubDevices).delay(1));
// get the "temperature-component" element by CSS selector (e.g., by class name)
de = fixture.debugElement.query(By.css('.temperature-component'));
el = de.nativeElement;
});
it('should show device readings after getRecentTemperatures subscribe (fakeAsync)', fakeAsync(() => {
fixture.detectChanges();
expect(spyRecentTemps.calls.any()).toBe(true, 'getRecentTemperatures called');
tick(1000);
fixture.detectChanges();
expect(el.textContent).toContain(stubDevices[0].temperatures[0].timestamp);
expect(el.textContent).toContain(stubDevices[0].temperatures[0].value);
}));
it('should show device readings after getRecentTemperatures subscribe (async)', async(() => {
fixture.detectChanges();
expect(spyRecentTemps.calls.any()).toBe(true, 'getRecentTemperatures called');
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(el.textContent).toContain(stubDevices[0].temperatures[0].timestamp);
expect(el.textContent).toContain(stubDevices[0].temperatures[0].value);
});
}));
it('should show device readings after getRecentTemperatures subscribe (async) (done)', (done) => {
async(() => {
fixture.detectChanges();
expect(spyRecentTemps.calls.any()).toBe(true, 'getRecentTemperatures called');
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(el.textContent).toContain(stubDevices[0].temperatures[0].timestamp);
expect(el.textContent).toContain(stubDevices[0].temperatures[0].value);
}).then(done);
});
});
});
fakeAsync fails with: 'Error: 1 timer(s) still in the queue.'
async fails with: 'Error: Cannot use setInterval from within an async zone test.'
async (done) fails with: 'Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'
How would I go about testing components with a async service dependency?
From what I understand it might be something about the AsyncScheduler within the Rx library using Date().now instead of faked time (https://github.com/angular/angular/issues/10127). If so has this been fixed? Or anyone found a workaround?
I'm using angular-cli: 1.0.0-beta.16. node: 4.4.2. npm: 3.10.6. webpack 2.1.0-beta.22.
I had ..
import 'rxjs/add/operator/timeout';
return this.http[method](url, emit, this.options)
.timeout(Config.http.timeout, new Error('timeout'))
Which was causing this error. I believe under the hood RXJS .timeout is calling setInterval.
I fixed this by switching ...
it('blah', async(() => {
to
it('blah', (done) => {
I would like to change the value of an input field from within an Angular 2 unit test.
<input type="text" class="form-control" [(ngModel)]="abc.value" />
I can't just change the ngModel because abc object is private:
private abc: Abc = new Abc();
In Angular 2 testing, can I simulate the user typing into the input field so that the ngModel will be updated with what the user has typed from within a unit test?
I can grab the DebugElement and the nativeElement of the input field without a problem. (Just setting a the value property on the nativeElement of the input field doesn't seem to work as it doesn't update the ngModel with what I've set for the value).
Maybe inputDebugEl.triggerEventHandler can be called, but I'm not sure what arguments to give it so it will simulate the user having typed a particular string of input.
You're right that you can't just set the input, you also need to dispatch the 'input' event. Here is a function I wrote earlier this evening to input text:
function sendInput(text: string) {
inputElement.value = text;
inputElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
return fixture.whenStable();
}
Here fixture is the ComponentFixture and inputElement is the relevant HTTPInputElement from the fixture's nativeElement. This returns a promise, so you'll probably have to resolve it sendInput('whatever').then(...).
In context: https://github.com/textbook/known-for-web/blob/52c8aec4c2699c2f146a33c07786e1e32891c8b6/src/app/actor/actor.component.spec.ts#L134
Update:
We had some issues getting this to work in Angular 2.1, it didn't like creating a new Event(...), so instead we did:
import { dispatchEvent } from '#angular/platform-browser/testing/browser-util';
...
function sendInput(text: string) {
inputElement.value = text;
dispatchEvent(fixture.nativeElement, 'input');
fixture.detectChanges();
return fixture.whenStable();
}
The accepted solution didn't quite work for me in Angular 2.4. The value I had set was not appearing in the (test) UI, even after detectChanges() was called.
The way I got it to work was to set up my test as follows:
describe('TemplateComponent', function () {
let comp: TemplateComponent;
let fixture: ComponentFixture<TemplateComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ FormsModule ],
declarations: [ TemplateComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TemplateComponent);
comp = fixture.componentInstance;
});
it('should allow us to set a bound input field', fakeAsync(() => {
setInputValue('#test2', 'Tommy');
expect(comp.personName).toEqual('Tommy');
}));
// must be called from within fakeAsync due to use of tick()
function setInputValue(selector: string, value: string) {
fixture.detectChanges();
tick();
let input = fixture.debugElement.query(By.css(selector)).nativeElement;
input.value = value;
input.dispatchEvent(new Event('input'));
tick();
}
});
My TemplateComponent component has a property named personName in this example, which was the model property I am binding to in my template:
<input id="test2" type="text" [(ngModel)]="personName" />
I also had trouble getting jonrsharpe's answer to work with Angular 2.4. I found that the calls to fixture.detectChanges() and fixture.whenStable() caused the form component to reset. It seems that some initialization function is still pending when the test starts. I solved this by adding extra calls to these methods before each test. Here is a snippet of my code:
beforeEach(() => {
TestBed.configureTestingModule({
// ...etc...
});
fixture = TestBed.createComponent(LoginComponent);
comp = fixture.componentInstance;
usernameBox = fixture.debugElement.query(By.css('input[name="username"]'));
passwordBox = fixture.debugElement.query(By.css('input[type="password"]'));
loginButton = fixture.debugElement.query(By.css('.btn-primary'));
formElement = fixture.debugElement.query(By.css('form'));
});
beforeEach(async(() => {
// The magic sauce!!
// Because this is in an async wrapper it will automatically wait
// for the call to whenStable() to complete
fixture.detectChanges();
fixture.whenStable();
}));
function sendInput(inputElement: any, text: string) {
inputElement.value = text;
inputElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
return fixture.whenStable();
}
it('should log in correctly', async(() => {
sendInput(usernameBox.nativeElement, 'User1')
.then(() => {
return sendInput(passwordBox.nativeElement, 'Password1')
}).then(() => {
formElement.triggerEventHandler('submit', null);
fixture.detectChanges();
let spinner = fixture.debugElement.query(By.css('img'));
expect(Helper.isHidden(spinner)).toBeFalsy('Spinner should be visible');
// ...etc...
});
}));
Hi I'm relatively new to Angular2, Karma and Jasmine. Currently I'm using Angular 2 RC4 Jasmine 2.4.x
I have an Angular 2 service which periodically calls an http service like this:
getDataFromDb() { return Observable.timer(0, 2000).flatMap(() => {
return this.http.get(this.backendUrl)
.map(this.extractData)
.catch(this.handleError);
});
}
Now I want to test the functionality. For testing purposes I have just tested the "http.get" on a separate function without the Observable.timer by doing:
const mockHttpProvider = {
deps: [MockBackend, BaseRequestOptions],
useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
}
}
describe('data.service test suite', () => {
var dataFromDbExpected: any;
beforeEachProviders(() => {
return [
DataService,
MockBackend,
BaseRequestOptions,
provide(Http, mockHttpProvider),
];
});
it('http call to obtain data',
inject(
[DataService, MockBackend],
fakeAsync((service: DataService, backend: MockBackend) => {
backend.connections.subscribe((connection: MockConnection) => {
dataFromDbExpected = 'myData';
let mockResponseBody: any = 'myData';
let response = new ResponseOptions({ body: mockResponseBody });
connection.mockRespond(new Response(response));
});
const parsedData$ = service.getDataFromDb()
.subscribe(response => {
console.log(response);
expect(response).toEqual(dataFromDbExpected);
});
})));
});
I obviously want to test the whole function with the Observable.timer. I think one might want to use the TestScheduler from the rxjs framework, but how can I tell to only repeat the timer function for x times? I couln't find any documentation using it in the typescript context.
Edit: I'm using rxjs 5 beta 6
Edit: Added working example for Angular 2.0.0 final release:
describe('when getData', () => {
let backend: MockBackend;
let service: MyService;
let fakeData: MyData[];
let response: Response;
let scheduler: TestScheduler;
beforeEach(inject([Http, XHRBackend], (http: Http, be: MockBackend) => {
backend = be;
service = new MyService(http);
fakeData = [{myfake: 'data'}];
let options = new ResponseOptions({ status: 200, body: fakeData });
response = new Response(options);
scheduler = new TestScheduler((a, b) => expect(a).toEqual(b));
const originalTimer = Observable.timer;
spyOn(Observable, 'timer').and.callFake(function (initialDelay, dueTime) {
return originalTimer.call(this, initialDelay, dueTime, scheduler);
});
}));
it('Should do myTest', async(inject([], () => {
backend.connections.subscribe((c: MockConnection) => c.mockRespond(response));
scheduler.schedule(() => {
service.getMyData().subscribe(
myData => {
expect(myData.length).toBe(3,
'should have expected ...');
});
}, 2000, null);
scheduler.flush();
})));
});
You need to inject the TestScheduler into the timer method inside a beforeEach part:
beforeEach(function() {
this.scheduler = new TestScheduler();
this.scheduler.maxFrames = 5000; // Define the max timespan of the scheduler
const originalTimer = Observable.timer;
spyOn(Observable, 'timer').and.callFake(function(initialDelay, dueTime) {
return originalTimer.call(this, initialDelay, dueTime, this.scheduler);
});
});
After that you have full control of the time with scheduleAbsolute:
this.scheduler.schedule(() => {
// should have been called once
// You can put your test code here
}, 1999, null);
this.scheduler.schedule(() => {
// should have been called twice
// You can put your test code here
}, 2000, null);
this.scheduler.schedule(() => {
// should have been called three times
// You can put your test code here
}, 4000, null);
this.scheduler.flush();
You need scheduler.flush() to start the TestScheduler.
edit: so if you want to only test it X times, use the schedule functions as often (and with the right absolute times in milliseconds) as you wish.
edit2: I added the missing scheduler start
edit3: I changed it so should be working with RxJs5
edit4: Add maxFrames setting since the default is 750ms and will prevent testing longer-running sequences.
I had issues with the TestScheduler() approach because the schedule() arrow function would never execute, so I found another path.
The Observable.timer function just returns an Observable, so I created one from scratch to give me complete control.
First, create a var for the observer:
let timerObserver: Observer<any>;
Now in the beforeEach() create the spy and have it return an Observable. Inside the Observable, save your instance to the timer:
beforeEach(() => {
spyOn(Observable, 'timer').and.returnValue(Observable.create(
(observer => {
timerObserver = observer;
})
));
});
In the test, just trigger the Observable:
it('Some Test',()=>{
// do stuff if needed
// trigger the fake timer using the Observer reference
timerObserver.next('');
timerObserver.complete();
expect(somethingToHappenAfterTimerCompletes).toHaveBeenCalled();
});
You can test Observable timers pretty easily with fakeAsync(). Here's a component that displays a countdown timer (using a momentJS duration):
timeout.component.ts
#Component({
selector: 'app-timeout-modal',
templateUrl: './timeout-modal.component.html'
})
export class TimeoutModalComponent implements OnInit {
countdownTimer: Observable<number>;
countdownSubscription: Subscription;
durationLeft = moment.duration(60000); // millis - 60 seconds
ngOnInit() {
this.countdownTimer = Observable.timer(0, 1000);
this.countdownSubscription = this.countdownTimer
.do(() => this.durationLeft.subtract(1, 's'))
.takeWhile(seconds => this.durationLeft.asSeconds() >= 0)
.subscribe(() => {
if (this.durationLeft.asSeconds() === 0) {
this.logout();
}
});
}
}
timeout.component.spec.ts
beforeEach(async(() => {
...
}));
beforeEach(() => {
fixture = TestBed.createComponent(TimeoutModalComponent);
component = fixture.componentInstance;
});
it('should show a count down', fakeAsync(() => {
fixture.detectChanges();
expect(component.durationLeft.asSeconds()).toEqual(60);
tick(1000);
fixture.detectChanges();
expect(component.durationLeft.asSeconds()).toEqual(59);
component.countdownSubscription.unsubscribe();
}));
I was struggling with this for a while also. Since apparently a lot has changed in the frameworks since this question was asked, I thought maybe someone would be helped by my solution. My project uses rxjs 5, jasmine 2.8 and angular 5.
In my component a timer was used to call a http-get function in a service every minute. My problem was that when using fakeAsync zone the (stubbed) get function was never called and I received the error: "Error: 1 periodic timer(s) still in the queue.".
The error is showing up because the timer keeps firing and isn't stopped at the end of the test. This can be resolved by adding "discardPeriodicTasks();" to the end of the test, which causes the timer to stop. Tick(); can be used to fake to passage of time untill a next call. I used a spy on my get-function in my service to see if it worked:
it(
'should call getTickets from service every .. ms as defined in refreshTime',
fakeAsync(() => {
fixture.detectChanges();
tick();
expect(getTicketsSpy).toHaveBeenCalledTimes(1);
// let 2 * refreshtime pass
tick(2 * component.refreshTime);
expect(getTicketsSpy).toHaveBeenCalledTimes(3);
discardPeriodicTasks();
})
);
The refreshTime is the parameter that I used in the timer. I hope this prevents someone from spending half a day trying to figure this out.