Simple question:
I've followed the tutorial: here where I have the following code:
this.term.valueChanges
.debounceTime(400)
.distinctUntilChanged()
.switchMap(term => this.wikipediaService.search(term));
Where term is a Control inheriting from AbstractControl, how can I trigger the valueChanges observable property, so that I can perform unit tests?
I think I was experiencing the same issue.
I was trying to test that heroService.searchHeroes was called:
ngOnInit() {
this.heroes$ = this.searchTerms
.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap((term: string) => this.heroService.searchHeroes(term))
);
}
I was able to fix it by only calling tick(500) once
it('should call HeroService.searchHeroes', fakeAsync(() => {
spyOn(heroService, 'searchHeroes').and.returnValue(component.heroes$);
let searchField: DebugElement = fixture.debugElement.query(By.css('#search-box'));
searchField.nativeElement.value = 'i';
searchField.nativeElement.dispatchEvent(new Event('keyup'));
tick(500);
expect(heroService.searchHeroes).toHaveBeenCalled();
}));
I found a work around in the following issue:
https://github.com/angular/angular/issues/8251
Essentially there's buggy behavior, but you can get a basic test working with code like this:
it("filter input changed should update filter string",
fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
let fixture = tcb.createFakeAsync(MyComponent);
let component = fixture.componentInstance as MyComponent;
fixture.detectChanges();
// Trigger a new value to emit on the valueChanges observable
//
component.filter.updateValue("testing");
// There's buggy behavior with multiple async events so we
// need to run tick() twice to get our desired behavior
// https://github.com/angular/angular/issues/8251
//
tick();
tick(500);
// Now we can inspect the results of the subscription in the
// original component
//
expect(component.filterString).toBe("testing");
})));
...and this is with a component that looks like this:
export class MyComponent {
filter = new Control();
/** The current filter string */
filterString: string;
constructor() {
// Subscribe to changes in the filter string
//
this.filter.valueChanges
.debounceTime(200)
.distinctUntilChanged()
.subscribe((filterString: string) => {
this.filterString = filterString;
});
}
}
Related
I'm new to ember and trying to figure out how to unit test, using sinon, the sessionStorage based on url parameters when that page is visited. I've tried a few things but still can't get the desired result. It passes even if I change the 'sessionValue' without editing the query param.
Thank you in advance.
ember component
beforeModel(transition) {
//transition will contain an object containing a query parameter. '?userid=1234' and is set in the sessionStorage.
if(transition.queryparam.hasOwnProperty('userid')){
sessionStorage.setItem('user:id', transition.queryparam)
}
}
Ember test
test('Session Storage contains query param value', async assert => {
let sessionKey = "user:id";
let sessionValue = "1234"
let store = {};
const mockLocalStorage = {
getItem: (key) => {
return key in store ? store[key] : null;
},
setItem: (key, value) => {
store[key] = `${value}`;
},
clear: () => {
store = {};
}
};
asserts.expect(1);
let spy = sinon.spy(sessionStorage, "setItem");
spy.calledWith(mockLocalStorage.setItem);
let stub = sinon.stub(sessionStorage, "getItem");
stub.calledWith(mockLocalStorage.getItem);
stub.returns(sessionValue);
await visit('/page?userid=1234');
mockLocalStorage.setItem(sessionKey, sessionValue);
assert.equal(mockLocalStorage.getItem(sessionKey), sessionValue, 'storage contains value');
})
Welcome to Ember!
There are many ways to test, and the below suggestion is one way (how I would approach interacting with the SessionStorage).
Instead of re-creating the SessionStorage API in your test, how do you feel about using a pre-made proxy around the Session Storage? (ie: "Don't mock what you don't own")
Using: https://github.com/CrowdStrike/ember-browser-services/#sessionstorage
Your app code would look like:
#service('browser/session-storage') sessionStorage;
beforeModel(transition) {
// ... details omitted ...
// note the addition of `this` -- the apis are entirely the same
// as SessionStorage
this.sessionStorage.setItem('user:id', ...)
}
then in your test:
module('Scenario Name', function (hooks) {
setupApplicationTest(hooks);
setupBrowserFakes(hooks, { sessionStorage: true });
test('Session Storage contains query param value', async assert => {
let sessionKey = "user:id";
let sessionValue = "1234"
let sessionStorage = this.owner.lookup('browser/session-storage');
await visit('/page?userid=1234');
assert.equal(sessionStorage.getItem(sessionKey), '1234', 'storage contains value');
});
})
With this approach, sinon isn't even needed :)
I am attempting to unit test a custom RxJS operator. The operator is very simple, it uses RetryWhen to retry a failed HTTP request, but has a delay and will only retry when the HTTP Error is in the 500 range. Using jasmine, and this is in an Angular application.
I've looked at this:
rxjs unit test with retryWhen
Unfortunately, updating the SpyOn call doesn't seem to change the returned observable on successive retries. Each time it retries it is retrying with the original spyon Value.
I have also looked at a bunch of rxjs marble examples, none of which seem to work. I am not sure it is possible to use rxjs marbles here, because (AFAIK) there is no way to simulate a situation where you first submit an errored observable, then submit a successful observable on subsequent tries.
The code is basically a clone of this:
https://blog.angularindepth.com/retry-failed-http-requests-in-angular-f5959d486294
export function delayedRetry(delayMS: number, maxRetry) {
let retries = maxRetry;
return (src: Observable<any>) =>
src.pipe(
retryWhen((errors: Observable<any>) => errors.pipe(
delay(delayMS),
mergeMap(error =>
(retries-- > 0 && error.status >= 500) ? of(error) : throwError(error))
))
);
}
I would like to be able to demonstrate that it can subscribe to an observable that returns an error on the first attempt, but then returns a successful response. The end subscription should show whatever success value the observable emits.
Thank you in advance for any insights.
try use this observable as source observable to test
const source = (called,successAt)=>{
return defer(()=>{
if(called<successAt){
called++
return throwError({status:500})
}
else return of(true)
})
}
test
this.delayedRetry(1000,3)(source(0,3)).subscribe()
To test the retry functionality, you need a observable which emits different events each time you call it. For example:
let alreadyCalled = false;
const spy = spyOn<any>(TestBed.inject(MyService), 'getObservable').and.returnValue(
new Observable((observer) => {
if (alreadyCalled) {
observer.next(message);
}
alreadyCalled = true;
observer.error('error message');
})
);
This observable will emit an error first and after that a next event.
You can check, if your observable got the message like this:
it('should retry on error', async(done) => {
let alreadyCalled = false;
const spy = spyOn<any>(TestBed.inject(MyDependencyService), 'getObservable').and.returnValue(
new Observable((observer) => {
if (alreadyCalled) {
observer.next(message);
}
alreadyCalled = true;
observer.error('error message');
})
);
const observer = {
next: (result) => {
expect(result.value).toBe(expectedResult);
done();
}
}
subscription = service.methodUnderTest(observer);
expect(spy).toHaveBeenCalledTimes(1);
}
Building on a previous answer I have been using this, which gives you more control over what's returned.
const source = (observables) => {
let count = 0;
return defer(() => {
return observables[count++];
});
};
Which can then be used like this
const obsA = source([
throwError({status: 500}),
of(1),
]);
Or it can then be used with rxjs marbles like
const obsA = source([
cold('--#', null, { status: 500 }),
cold('--(a|)', { a: 1 }),
]);
We have some TypeScript code using the Aurelia framework and Dialog plugin that we are trying to test with Jasmine, but can't work out how to do properly.
This is the source function:
openDialog(action: string) {
this._dialogService.open({ viewModel: AddAccountWizard })
.whenClosed(result => {
if (!result.wasCancelled && result.output) {
const step = this.steps.find((i) => i.action === action);
if (step) {
step.isCompleted = true;
}
}
});
}
We can create a DialogService spy, and verify the open method easily - but we can't work out how to make the spy invoke the whenClosed method with a mocked result parameter so that we can then assert that the step is completed.
This is the current Jasmine code:
it("opens a dialog when clicking on incomplete bank account", async done => {
// arrange
arrangeMemberVerificationStatus();
await component.create(bootstrap);
const vm = component.viewModel as GettingStartedCustomElement;
dialogService.open.and.callFake(() => {
return { whenClosed: () => Promise.resolve({})};
});
// act
$(".link, .-arrow")[0].click();
// assert
expect(dialogService.open).toHaveBeenCalledWith({ viewModel: AddAccountWizard });
expect(vm.steps[2].isCompleted).toBeTruthy(); // FAILS
done();
});
We've just recently updated our DialogService and ran into the same issue, so we've made this primitive mock that suited our purposes so far. It's fairly limited and doesn't do well for mocking multiple calls with different results, but should work for your above case:
export class DialogServiceMock {
shouldCancelDialog = false;
leaveDialogOpen = false;
desiredOutput = {};
open = () => {
let result = { wasCancelled: this.shouldCancelDialog, output: this.desiredOutput };
let closedPromise = this.leaveDialogOpen ? new Promise((r) => { }) : Promise.resolve(result);
let resultPromise = Promise.resolve({ closeResult: closedPromise });
resultPromise.whenClosed = (callback) => {
return this.leaveDialogOpen ? new Promise((r) => { }) : Promise.resolve(typeof callback == "function" ? callback(result) : null);
};
return resultPromise;
};
}
This mock can be configured to test various responses, when a user cancels the dialog, and scenarios where the dialog is still open.
We haven't done e2e testing yet, so I don't know of a good way to make sure you wait until the .click() call finishes so you don't have a race condition between your expect()s and the whenClosed() logic, but I think you should be able to use the mock in the test like so:
it("opens a dialog when clicking on incomplete bank account", async done => {
// arrange
arrangeMemberVerificationStatus();
await component.create(bootstrap);
const vm = component.viewModel as GettingStartedCustomElement;
let mockDialogService = new MockDialogService();
vm.dialogService = mockDialogService; //Or however you're injecting the mock into the constructor; I don't see the code where you're doing that right now.
spyOn(mockDialogService, 'open').and.callThrough();
// act
$(".link, .-arrow")[0].click();
browser.sleep(100)//I'm guessing there's a better way to verify that it's finished with e2e testing, but the point is to make sure it finishes before we assert.
// assert
expect(mockDialogService.open).toHaveBeenCalledWith({ viewModel: AddAccountWizard });
expect(vm.steps[2].isCompleted).toBeTruthy(); // FAILS
done();
});
I have a slide-show component that has an Input array of slide objects and shows each one as long as it's been defined in slide.time of itself. also there are two buttons that clicking them has to slide to the next item and reset the timer. in order to make this work, I'm using Observables like this:
/**
* a "SUBJECT" for pausing(restarting) the slider's auto-slide on user's click on left and right arrows
* #type {Subject}
*/
private pauser = new Subject();
/**
* the main observable for timer (before adding the pause/reset option)
* #type {Observable<T>}
*/
private source = Observable
.interval(1000)
.timeInterval()
.map(function (x) { /*return x.value + ':' + x.interval;*/ return x })
.share();
/**
* the final timer, which can be paused
* #type {Observable<R>}
*/
private pausableSource = this.pauser.switchMap(paused => paused ? Observable.never() : this.source);
/**
* the subscription to the timer which is assigned at OnInit hook , and destroyed at OnDestroy
*/
private subscription;
ngOnInit(){
this.subscription = this.pausableSource.subscribe(() => {
//doing changes to the template and changing between slides
});
this.pauser.next(false);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
and it's working properly.
now to test this component, I'm giving it some data within a test-host component and want to check it's functionality like this:
it("should show the second (.slidingImg img) element after testHost.data[0].time seconds
have passed (which here, is 2 seconds)", () => {
//testing
});
i tried many things that i found in docs or anywhere on the internet, but none of them work for me. the problem is that I can't mock the passage of time in a way that the observable timer would perform next actions, and it's like no time has passed whatsoever. two of the ways that haven't worked for me are these:
it("should show the second (.slidingImg img) element after testHost.data[0].time seconds
have passed (which here, is 2 seconds)", fakeAsync(() => {
fixture.detectChanges();
tick(2500);
flushMicrotasks();
fixture.detectChanges();
let secondSlidingImg = fixture.debugElement.queryAll(By.css('.slidingImg'))[1].query(By.css('img'));
expect(secondSlidingImg).toBeTruthy();
//error: expected null to be truthy
}));
i got this from angular2 docs.
and:
beforeEach(() => {
fixture = TestBed.createComponent(TestHostComponent);
testHost = fixture.componentInstance;
scheduler = new TestScheduler((a, b) => expect(a).toEqual(b));
const originalTimer = Observable.interval;
spyOn(Observable, 'interval').and.callFake(function(initialDelay, dueTime) {
return originalTimer.call(this, initialDelay, dueTime, scheduler);
});
// or:
// const originalTimer = Observable.timer;
// spyOn(Observable, 'timer').and.callFake(function(initialDelay, dueTime) {
// return originalTimer.call(this, initialDelay, dueTime, scheduler);
// });
scheduler.maxFrames = 5000;
});
it("should show the second (.slidingImg img) element after testHost.data[0].time seconds
have passed (which here, is 2 seconds)", async(() => {
scheduler.schedule(() => {
fixture.detectChanges();
let secondSlidingImg = fixture.debugElement.queryAll(By.css('.slidingImg'))[1].query(By.css('img'));
expect(secondSlidingImg).toBeTruthy();
//error: expected null to be truthy
}, 2500, null);
scheduler.flush();
}));
i got this approach from this question.
so I desperately need to know how exactly should I simulate time passage in my unit test so that the component's observable time interval would really trigger...
versions:
angular: "2.4.5"
"rxjs": "5.0.1"
"jasmine": "~2.4.1"
"karma": "^1.3.0"
"typescript": "~2.0.10"
"webpack": "2.2.1"
fakeAsync doesn't work for some case with RxJs. You need to manually move internal timer in RxJs. Something along those lines:
import {async} from 'rxjs/internal/scheduler/async';
...
describe('faking internal RxJs scheduler', () => {
let currentTime: number;
beforeEach(() => {
currentTime = 0;
spyOn(async, 'now').and.callFake(() => currentTime);
});
it('testing RxJs delayed execution after 1000ms', fakeAsync(() => {
// Do your stuff
fixture.detectChanges();
currentTime = 1000;
tick(1000);
discardPeriodicTasks();
expect(...);
}));
});
Best way I found for testing this is Marble Testing:
Tutorial: https://medium.com/#bencabanes/marble-testing-observable-introduction-1f5ad39231c
You can control emits by order and time, which seems to be exactly what you need.
This may be a duplicate but I have looked at a lot of other questions here and they usually miss what I am looking for in some way. They mostly talk about a service they created themselves. That I can do and have done. I am trying to override what angular is injecting with my mock. I thought it would be the same but for some reason when I step through the code it is always the angular $cookieStore and not my mock.
I have very limited experience with jasmine and angularjs. I come from a C# background. I usually write unit tests moq (mocking framework for C#). I am use to seeing something like this
[TestClass]
public PageControllerTests
{
private Mock<ICookieStore> mockCookieStore;
private PageController controller;
[TestInitialize]
public void SetUp()
{
mockCookieStore = new Mock<ICookieStore>();
controller = new PageController(mockCookieStore.Object);
}
[TestMethod]
public void GetsCarsFromCookieStore()
{
// Arrange
mockCookieStore.Setup(cs => cs.Get("cars"))
.Return(0);
// Act
controller.SomeMethod();
// Assert
mockCookieStore.VerifyAll();
}
}
I want mock the $cookieStore service which I use in one of my controllers.
app.controller('PageController', ['$scope', '$cookieStore', function($scope, $cookieStore) {
$scope.cars = $cookieStore.get('cars');
if($scope.cars == 0) {
// Do other logic here
.
}
$scope.foo = function() {
.
.
}
}]);
I want to make sure that the $cookieStore.get method is invoked with a 'garage' argument. I also want to be able to control what it gives back. I want it to give back 0 and then my controller must do some other logic.
Here is my test.
describe('Controller: PageController', function () {
var controller,
scope,
cookieStoreSpy;
beforeEach(function () {
cookieStoreSpy = jasmine.createSpyObj('CookieStore', ['get']);
cookieStoreSpy.get.andReturn(function(key) {
switch (key) {
case 'cars':
return 0;
case 'bikes':
return 1;
case 'garage':
return { cars: 0, bikes: 1 };
}
});
module(function($provide) {
$provide.value('$cookieStore', cookieStoreSpy);
});
module('App');
});
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller;
}));
it('Gets car from cookie', function () {
controller('PageController', { $scope: scope });
expect(cookieStoreSpy.get).toHaveBeenCalledWith('cars');
});
});
This is a solution for the discussion we had in my previous answer.
In my controller I'm using $location.path and $location.search. So to overwrite the $location with my mock I did:
locationMock = jasmine.createSpyObj('location', ['path', 'search']);
locationMock.location = "";
locationMock.path.andCallFake(function(path) {
console.log("### Using location set");
if (typeof path != "undefined") {
console.log("### Setting location: " + path);
this.location = path;
}
return this.location;
});
locationMock.search.andCallFake(function(query) {
console.log("### Using location search mock");
if (typeof query != "undefined") {
console.log("### Setting search location: " + JSON.stringify(query));
this.location = JSON.stringify(query);
}
return this.location;
});
module(function($provide) {
$provide.value('$location', locationMock);
});
I didn't have to inject anything in the $controller. It just worked. Look at the logs:
LOG: '### Using location set'
LOG: '### Setting location: /test'
LOG: '### Using location search mock'
LOG: '### Setting search location: {"limit":"50","q":"ani","tags":[1,2],"category_id":5}'
If you want to check the arguments, spy on the method
// declare the cookieStoreMock globally
var cookieStoreMock;
beforeEach(function() {
cookieStoreMock = {};
cookieStoreMock.get = jasmine.createSpy("cookieStore.get() spy").andCallFake(function(key) {
switch (key) {
case 'cars':
return 0;
case 'bikes':
return 1;
case 'garage':
return {
cars: 0,
bikes: 1
};
}
});
module(function($provide) {
$provide.value('cookieStore', cookieStoreMock);
});
});
And then to test the argument do
expect(searchServiceMock.search).toHaveBeenCalledWith('cars');
Here is an example https://github.com/lucassus/angular-seed/blob/81d820d06e1d00d3bae34b456c0655baa79e51f2/test/unit/controllers/products/index_ctrl_spec.coffee#L3 it's coffeescript code with mocha + sinon.js but the idea is the same.
Basically with the following code snippet you could load a module and substitute its services:
beforeEach(module("myModule", function($provide) {
var stub = xxx; //... create a stub here
$provide.value("myService", stub);
}));
Later in the spec you could inject this stubbed service and do assertions:
it("does something magical", inject(function(myService) {
subject.foo();
expect(myService).toHaveBeenCalledWith("bar");
}));
More details and tips about mocking and testing you could find in this excellent blog post: http://www.yearofmoo.com/2013/09/advanced-testing-and-debugging-in-angularjs.html
Why mock cookieStore when you may use it directly without modification? The code below is a partial unit test for a controller which uses $cookieStore to put and get cookies. If your controller has a method known as "setACookie" that uses $cookieStore.put('cookieName', cookieValue) ... then the test should be able to read the value that was set.
describe('My controller', function() {
var $cookieStore;
describe('MySpecificController', function() {
beforeEach(inject(function(_$httpBackend_, $controller, _$cookieStore_) {
$cookieStore = _$cookieStore_;
// [...] unrelated to cookieStore
}));
it('should be able to reference cookies now', function () {
scope.setACookie();
expect($cookieStore.get('myCookieName')).toBe('setToSomething');
});
});