How to write jasmine test to custom directive which is listenering to 'mouseup' event in Angular - karma-jasmine

I am new to writing unit test with Angular + Jasmine.
I have a custom directive and trying to write a unit test for it.
My directive has the following HostListener. I am not sure how to pass the 'event' object to this in spec.ts file
#HostListener('mouseup', ['$event'])
clickEvent(event: MouseEvent) {
if (this.hasIgnoreAttribute(event.target)) return; //event.target is undefined here.so i am not able to test the switch case. how to pass target and button properties from test?
switch (event.button) {
case 0: // Left click
this.navigate();
return;
case 1: {
// Middle click
this.openNewTab();
return;
}
}
}
spec.ts
beforeEach(() => {
spyRouter = jasmine.createSpyObj('Router', ['navigate']);
TestBed.configureTestingModule({
declarations: [RowClickTestComponent, RowClickDirective],
providers: [
{ provide: ContextMenuService, useValue: spyContextMenuService },
{ provide: Router, useValue: spyRouter }
]
});
fixture = TestBed.createComponent(RowClickTestComponent);
component = fixture.componentInstance;
inputEl = fixture.debugElement.query(By.css('tr'));
});
fit('mouseup on row', () => {
inputEl.triggerEventHandler('mouseup', { } as MouseEvent); //how to pass the 'target'
fixture.detectChanges();
});

Related

How to update same fixture file within the same BeforEeach hook and get the updated details as the response in Cypress [duplicate]

I am trying to write a test with the new cypress 6 interceptor method (Cypress API Intercept). For the test I am writing I need to change the reponse of one endpoint after some action was performed.
Expectation:
I am calling cy.intercept again with another fixture and expect it to change all upcomming calls to reponse with this new fixture.
Actual Behaviour:
Cypress still response with the first fixture set for the call.
Test Data:
In a test project I have recreated the problem:
test.spec.js
describe('testing cypress', () => {
it("multiple responses", () => {
cy.intercept('http://localhost:4200/testcall', { fixture: 'example.json' });
// when visiting the page it makes one request to http://localhost:4200/testcall
cy.visit('http://localhost:4200');
cy.get('.output').should('contain.text', '111');
// now before the button is clicked and the call is made again
// cypress should change the response to the other fixture
cy.intercept('http://localhost:4200/testcall', { fixture: 'example2.json' });
cy.get('.button').click();
cy.get('.output').should('contain.text', '222');
});
});
example.json
{
"text": "111"
}
example2.json
{
"text": "222"
}
app.component.ts
import { HttpClient } from '#angular/common/http';
import { AfterViewInit, Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
public text: string;
public constructor(private httpClient: HttpClient) { }
public ngAfterViewInit(): void {
this.loadData();
}
public loadData(): void {
const loadDataSubscription = this.httpClient.get<any>('http://localhost:4200/testcall').subscribe(response => {
this.text = response.body;
loadDataSubscription.unsubscribe();
});
}
}
app.component.html
<button class="button" (click)="loadData()">click</button>
<p class="output" [innerHTML]="text"></p>
Slightly clumsy, but you can use one cy.intercept() with a Function routeHandler, and count the calls.
Something like,
let interceptCount = 0;
cy.intercept('http://localhost:4200/testcall', (req) => {
req.reply(res => {
if (interceptCount === 0 ) {
interceptCount += 1;
res.send({ fixture: 'example.json' })
} else {
res.send({ fixture: 'example2.json' })
}
});
});
Otherwise, everything looks good in your code so I guess over-riding an intercept is not a feature at this time.
As of Cypress v7.0.0 released 04/05/2021, cy.intercept() allows over-riding.
We introduced several breaking changes to cy.intercept().
Request handlers supplied to cy.intercept() are now matched starting with the most recently defined request interceptor. This allows users to override request handlers by calling cy.intercept() again.
So your example code above now works
cy.intercept('http://localhost:4200/testcall', { fixture: 'example.json' });
// when visiting the page it makes one request to http://localhost:4200/testcall
cy.visit('http://localhost:4200');
cy.get('.output').should('contain.text', '111');
// now cypress should change the response to the other fixture
cy.intercept('http://localhost:4200/testcall', { fixture: 'example2.json' });
cy.get('.button').click();
cy.get('.output').should('contain.text', '222');
Cypress command cy.intercept has the
times parameter that you can use to create intercepts that only are used N times. In your case it would be
cy.intercept('http://localhost:4200/testcall', {
fixture: 'example.json',
times: 1
});
...
cy.intercept('http://localhost:4200/testcall', {
fixture: 'example2.json',
times: 1
});
See the cy.intercept example in the Cypress recipes repo https://github.com/cypress-io/cypress-example-recipes#network-stubbing-and-spying
const requestsCache = {};
export function reIntercept(type: 'GET' | 'POST' | 'PUT' | 'DELETE', url, options: StaticResponse) {
requestsCache[type + url] = options;
cy.intercept(type, url, req => req.reply(res => {
console.log(url, ' => ', requestsCache[type + url].fixture);
return res.send(requestsCache[type + url]);
}));
}
Make sure to clean requestsCache when needed.

How to test RxJs observable callback with NgRx setState

I would like to know how to test the code inside a .subscribe callback, with the subscription being on a NgRx store selector.
Environment: Angular 13, RxJs 7+, NgRx 13, Jest 27
Consider
my-component.ts
...
ngOnInit {
this.myValue = true;
this.store.select(mySelector).pipe(filter(data => data.attribute === true)).subscribe(data => {
this.myValue = false; // I want to test this
}
}
...
my-component.spec.ts
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
let store: MockStore;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [MyComponent],
imports: [...],
providers: [
provideMockStore({
initialState: { myFeature: { } },
}),
],
}).compileComponents();
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
store = TestBed.inject(MockStore);
});
it('should perform animation & redirect to /dashboard if login successful', () => {
store.setState({
myFeature: {
...
attribute: true,
},
});
expect(component.myValue).toBe(false);
});
This works, but it's random. Since this is asynchronous I could test myValue before the subscribe callback has been called and the test would fail, for example if my subscribe callback takes time to do stuff, like so (I'm adding a delay of 500ms) :
...
ngOnInit {
this.myValue = true;
this.store.select(mySelector).pipe(filter(data => data.attribute === true), delay(500)).subscribe(data => {
this.myValue = false; // I want to test this
}
}
...
This fails.
How can I wait the callback to perform before testing my value ? I could wait an arbitrary time like 1 sec before testing, but it could break at any time in the future it's not robust enough. Like :
it('should perform animation & redirect to /dashboard if login successful',
async () => {
store.setState({
myFeature: {
...
attribute: true,
},
});
await lastValueFrom(timer(600)); //rxjs 7
expect(component.myValue).toBe(false);
});
Thanks for your help
Use waitForAsync and fixture.whenStable to ensure completion of async tasks before evaluating the expect.
it('should perform animation & redirect to /dashboard if login successful', waitForAsync(() => {
store.setState({
myFeature: {
...
attribute: true,
},
});
fixture.whenStable()
.then(() => expect(component.myValue).toBe(false));
}));

How override Provider in Angular 5 for only one test?

In one of my unit test files, I have to mock several times the same service with different mocks.
import { MyService } from '../services/myservice.service';
import { MockMyService1 } from '../mocks/mockmyservice1';
import { MockMyService2 } from '../mocks/mockmyservice2';
describe('MyComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
MyComponent
],
providers: [
{ provide: MyService, useClass: MockMyService1 }
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MapComponent);
mapComponent = fixture.componentInstance;
fixture.detectChanges();
});
describe('MyFirstTest', () => {
it('should test with my first mock', () => {
/**
* Test with my first mock
*/
});
});
describe('MySecondTest', () => {
// Here I would like to change { provide: MyService, useClass: MockMyService1 } to { provide: MyService, useClass: MockMyService2 }
it('should test with my second mock', () => {
/**
* Test with my second mock
*/
});
});
});
I see that the function overrideProvider exists, but I did not manage to use it in my test. When I use it in a "it", the provider doesn't change. I didn't manage to find an example where this function is called. Could you explain me how to use it properly? Or have you an other method to do that?
As of angular 6 I noticed that overrideProvider works with the useValue property. So in order to make it work try something like:
class MockRequestService1 {
...
}
class MockRequestService2 {
...
}
then write you TestBed like:
// example with injected service
TestBed.configureTestingModule({
// Provide the service-under-test
providers: [
SomeService, {
provide: SomeInjectedService, useValue: {}
}
]
});
And whenever you want to override the provider just use:
TestBed.overrideProvider(SomeInjectedService, {useValue: new MockRequestService1()});
// Inject both the service-to-test and its (spy) dependency
someService = TestBed.get(SomeService);
someInjectedService = TestBed.get(SomeInjectedService);
Either in a beforeEach() function or place it in an it() function.
If you need TestBed.overrideProvider() with different values for different test cases, TestBed is frozen after call of TestBed.compileComponents() as #Benjamin Caure already pointed out. I found out that it is also frozen after call of TestBed.get().
As a solution in your 'main' describe use:
let someService: SomeService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{provide: TOKEN, useValue: true}
]
});
// do NOT initialize someService with TestBed.get(someService) here
}
And in your specific test cases use
describe(`when TOKEN is true`, () => {
beforeEach(() => {
someService = TestBed.get(SomeService);
});
it(...)
});
describe(`when TOKEN is false`, () => {
beforeEach(() => {
TestBed.overrideProvider(TOKEN, {useValue: false});
someService = TestBed.get(SomeService);
});
it(...)
});
If the service is injected as public property, e.g.:
#Component(...)
class MyComponent {
constructor(public myService: MyService)
}
You can do something like:
it('...', () => {
component.myService = new MockMyService2(...); // Make sure to provide MockMyService2 dependencies in constructor, if it has any.
fixture.detectChanges();
// Your test here...
})
If injected service is stored in a private property, you can write it as (component as any).myServiceMockMyService2 = new MockMyService2(...); to bypass TS.
It's not pretty but it works.
As for TestBed.overrideProvider, I had no luck with that approach (which would be much nicer if it worked):
it('...', () =>{
TestBed.overrideProvider(MyService, { useClass: MockMyService2 });
TestBed.compileComponents();
fixture = TestBed.createComponent(ConfirmationModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
// This was still using the original service, not sure what is wrong here.
});
I was facing similar problem, but in a simpler scenario, just one test(describe(...)) with multiple specifications(it(...)).
The solution that worked for me was postponing the TestBed.compileComponents and the TestBed.createComponent(MyComponent) commands.
Now I execute those on each individual test/specification, after calling TestBed.overrideProvider(...) when needed.
describe('CategoriesListComponent', () => {
...
beforeEach(async(() => {
...//mocks
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([])],
declarations: [CategoriesListComponent],
providers: [{provide: ActivatedRoute, useValue: mockActivatedRoute}]
});
}));
...
it('should call SetCategoryFilter when reload is false', () => {
const mockActivatedRouteOverride = {...}
TestBed.overrideProvider(ActivatedRoute, {useValue: mockActivatedRouteOverride });
TestBed.compileComponents();
fixture = TestBed.createComponent(CategoriesListComponent);
fixture.detectChanges();
expect(mockCategoryService.SetCategoryFilter).toHaveBeenCalledTimes(1);
});
Just for reference, if annynone meets this issue.
I tried to use
TestBed.overrideProvider(MockedService, {useValue: { foo: () => {} } });
it was not working, still the original service was injected in test (that with providedIn: root)
In test I used alias to import OtherService:
import { OtherService } from '#core/OtherService'`
while in the service itself I had import with relative path:
import { OtherService } from '../../../OtherService'
After correcting it so both test and service itself had same imports TestBed.overrideProvider() started to take effect.
Env: Angular 7 library - not application and jest
I needed to configure MatDialogConfig for two different test scenarios.
As others pointed out, calling compileCompents will not allow you to call overrideProviders. So my solution is to call compileComponents after calling overrideProviders:
let testConfig;
beforeEach(waitForAsync((): void => {
configuredTestingModule = TestBed.configureTestingModule({
declarations: [MyComponentUnderTest],
imports: [
MatDialogModule
],
providers: [
{ provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: { testConfig } }
]
});
}));
const buildComponent = (): void => {
configuredTestingModule.compileComponents(); // <-- compileComponents here
fixture = TestBed.createComponent(MyComponentUnderTest);
component = fixture.componentInstance;
fixture.detectChanges();
};
describe('with default mat dialog config', (): void => {
it('sets the message property in the component to the default', (): void => {
buildComponent(); // <-- manually call buildComponent helper before each test, giving you more control of when it is called.
expect(compnent.message).toBe(defaultMessage);
});
});
describe('with custom config', (): void => {
const customMessage = 'Some custom message';
beforeEach((): void => {
testConfig = { customMessage };
TestBed.overrideProvider(MAT_DIALOG_DATA, { useValue: testConfig }); //< -- override here, before compiling
buildComponent();
});
it('sets the message property to the customMessage value within testConfig', (): void => {
expect(component.message).toBe(customMessage);
});
});

How to correctly test effects in ngrx 4?

There are plenty of tutorials how to test effects in ngrx 3.
However, I've found only 1 or 2 for ngrx4 (where they removed the classical approach via EffectsTestingModule ), e.g. the official tutorial
However, in my case their approach doesn't work.
effects.spec.ts (under src/modules/list/store/list in the link below)
describe('addItem$', () => {
it('should return LoadItemsSuccess action for each item', async() => {
const item = makeItem(Faker.random.word);
actions = hot('--a-', { a: new AddItem({ item })});
const expected = cold('--b', { b: new AddUpdateItemSuccess({ item }) });
// comparing marbles
expect(effects.addItem$).toBeObservable(expected);
});
})
effects.ts (under src/modules/list/store/list in the link below)
...
#Effect() addItem$ = this._actions$
.ofType(ADD_ITEM)
.map<AddItem, {item: Item}>(action => {
return action.payload
})
.mergeMap<{item: Item}, Observable<Item>>(payload => {
return Observable.fromPromise(this._listService.add(payload.item))
})
.map<any, AddUpdateItemSuccess>(item => {
return new AddUpdateItemSuccess({
item,
})
});
...
Error
should return LoadItemsSuccess action for each item
Expected $.length = 0 to equal 1.
Expected $[0] = undefined to equal Object({ frame: 20, notification: Notification({ kind: 'N', value: AddUpdateItemSuccess({ payload: Object({ item: Object({ title: Function }) }), type: 'ADD_UPDATE_ITEM_SUCCESS' }), error: undefined, hasValue: true }) }).
at compare (webpack:///node_modules/jasmine-marbles/index.js:82:0 <- karma-test-shim.js:159059:33)
at Object.<anonymous> (webpack:///src/modules/list/store/list/effects.spec.ts:58:31 <- karma-test-shim.js:131230:42)
at step (karma-test-shim.js:131170:23)
NOTE: the effects use a service which involves writing to PouchDB. However, the issue doesn't seem related to that
and also the effects work in the running app.
The full code is a Ionic 3 app and be found here (just clone, npm i and npm run test)
UPDATE:
With ReplaySubject it works, but not with hot/cold marbles
const item = makeItem(Faker.random.word);
actions = new ReplaySubject(1) // = Observable + Observer, 1 = buffer size
actions.next(new AddItem({ item }));
effects.addItem$.subscribe(result => {
expect(result).toEqual(new AddUpdateItemSuccess({ item }));
});
My question was answered by #phillipzada at the Github issue I posted.
For anyone checking this out later, I report here the answer:
Looks like this is a RxJS issue when using promises using marbles. https://stackoverflow.com/a/46313743/4148561
I did manage to do a bit of a hack which should work, however, you will need to put a separate test the service is being called unless you can update the service to return an observable instead of a promise.
Essentially what I did was extract the Observable.fromPromise call into its own "internal function" which we can mock to simulate a call to the service, then it looks from there.
This way you can test the internal function _addItem without using marbles.
Effect
import 'rxjs/add/observable/fromPromise';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import { Injectable } from '#angular/core';
import { Actions, Effect } from '#ngrx/effects';
import { Action } from '#ngrx/store';
import { Observable } from 'rxjs/Observable';
export const ADD_ITEM = 'Add Item';
export const ADD_UPDATE_ITEM_SUCCESS = 'Add Item Success';
export class AddItem implements Action {
type: string = ADD_ITEM;
constructor(public payload: { item: any }) { }
}
export class AddUpdateItemSuccess implements Action {
type: string = ADD_UPDATE_ITEM_SUCCESS;
constructor(public payload: { item: any }) { }
}
export class Item {
}
export class ListingService {
add(item: Item) {
return new Promise((resolve, reject) => { resolve(item); });
}
}
#Injectable()
export class SutEffect {
_addItem(payload: { item: Item }) {
return Observable.fromPromise(this._listService.add(payload.item));
}
#Effect() addItem$ = this._actions$
.ofType<AddItem>(ADD_ITEM)
.map(action => action.payload)
.mergeMap<{ item: Item }, Observable<Item>>(payload => {
return this._addItem(payload).map(item => new AddUpdateItemSuccess({
item,
}));
});
constructor(
private _actions$: Actions,
private _listService: ListingService) {
}
}
Spec
import { cold, hot, getTestScheduler } from 'jasmine-marbles';
import { async, TestBed } from '#angular/core/testing';
import { Actions } from '#ngrx/effects';
import { Store, StoreModule } from '#ngrx/store';
import { getTestActions, TestActions } from 'app/tests/sut.helpers';
import { AddItem, AddUpdateItemSuccess, ListingService, SutEffect } from './sut.effect';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
describe('Effect Tests', () => {
let store: Store<any>;
let storeSpy: jasmine.Spy;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({})
],
providers: [
SutEffect,
{
provide: ListingService,
useValue: jasmine.createSpyObj('ListingService', ['add'])
},
{
provide: Actions,
useFactory: getTestActions
}
]
});
store = TestBed.get(Store);
storeSpy = spyOn(store, 'dispatch').and.callThrough();
storeSpy = spyOn(store, 'select').and.callThrough();
}));
function setup() {
return {
effects: TestBed.get(SutEffect) as SutEffect,
listingService: TestBed.get(ListingService) as jasmine.SpyObj<ListingService>,
actions$: TestBed.get(Actions) as TestActions
};
}
fdescribe('addItem$', () => {
it('should return LoadItemsSuccess action for each item', async () => {
const { effects, listingService, actions$ } = setup();
const action = new AddItem({ item: 'test' });
const completion = new AddUpdateItemSuccess({ item: 'test' });
// mock this function which we can test later on, due to the promise issue
spyOn(effects, '_addItem').and.returnValue(Observable.of('test'));
actions$.stream = hot('-a|', { a: action });
const expected = cold('-b|', { b: completion });
expect(effects.addItem$).toBeObservable(expected);
expect(effects._addItem).toHaveBeenCalled();
});
})
})
Helpers
import { Actions } from '#ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { empty } from 'rxjs/observable/empty';
export class TestActions extends Actions {
constructor() {
super(empty());
}
set stream(source: Observable<any>) {
this.source = source;
}
}
export function getTestActions() {
return new TestActions();
}

Mock #ngrx/store in angular 2

I am using store in my application like below and it works fine.
export class NavigationComponent {
navigationLinks$: Observable<Navigation[]>;
constructor(private store: Store<State>) {
this.navigationLinks$ = this.store.select('navigation')
.map((result: State) => result.navigationLinks);
}
Now, I am trying to create a unit test and want to mock this store. This is what i am doing:
1. Creating the Mock Store
Creating a mock store which will return mock data when this.store.select('') is called. The mockdata returns a property of array type called navigationLinks.
class StoreMock {
public dispatch(obj) {
console.log('dispatching from the mock store!')
}
public select(obj) {
console.log('selecting from the mock store!');
return Observable.of([
{ 'navigaitonLinks$': [{ 'name': 'Help', hasChild: false}] }
])
}
}
2. BeforeEach blocks
describe('NavigationComponent', () => {
let component: NavigationComponent;
let fixture: ComponentFixture<NavigationComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [NavigationComponent],
providers: [{provide: Store, useClass: StoreMock}],
imports: [
StoreModule.provideStore(reducers)
],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NavigationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
3. My test
I know this test will fail as per the expect statement but I am not able to populate the navigationLinks$ property with my mock data.
it(`should create the navigation with 'Help' link`, () => {
let navLinks: any[];
component.navigationLinks$.subscribe();
console.log(navLinks); // This should print my mockdata so i can assert it
expect(component.navigationLinks$).toEqual('Help');
});
The console.log prints undefined and is not able to read the data that MockStore select() is returning. Is there something extra I need to do?
I have the same issue, and I just return the object with Observable.of() function.
return Observable.of([
{ 'navigaitonLinks$': [{ 'name': 'Help', hasChild: false}] }
])
to
return Observable.of([{ 'name': 'Help', hasChild: false}, {}, {} ... ]);
This will populate your Observable object :
it(`should create the navigation with 'Help' link`, () => {
component.navigationLinks$.subscribe((links) => {
console.log(links); // This should print an array of Links
});
});