I can't test NgRx effects. Can you help me?
Friends, help me please. I want test some effect, but i can't. I get error "Expected $[0].notification.value.payload to be a kind of Object, but was User({ name: '1212', roles: [ 'somerole' ] })".
I don't understand what wrong.
effect:
#Injectable({
providedIn: 'root'
})
#Injectable()
export class AuthEffects {
constructor(
private actions$: Actions,
private rootService: RootService,
private router: Router,
) {
}
#Effect()
authUser$: Observable<any> = this.actions$.pipe(
ofType(authActions.FETCHING),
map((action: authActions.Fetching) => action.payload),
switchMap((paylod: UserRequest) => this.rootService.login(paylod)
.pipe(
map((value) => {
const {sub, authorities} = value;
this.router.navigate(['/customers-list']);
return new authActions.Success(new User(sub, authorities));
}),
catchError(() => of(new authActions.Fail('wrong username or password')))
)
)
);
}
spec:
describe('AuthEffects', () => {
let effects: AuthEffects;
let rootService: jasmine.SpyObj<RootService>;
let actions: Observable<any>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, RouterTestingModule],
providers: [
RootService,
AuthEffects,
provideMockActions(() => actions),
{
provide: RootService,
useValue: {
login: jasmine.createSpy()
}
}
]
});
effects = TestBed.get(AuthEffects);
rootService = TestBed.get(RootService);
});
it('should work', () => {
const userRequest: UserRequest = {
name: '1212',
password: 'alsj'
};
const userResponse: UserResponse = {
sub: '1212',
authorities: ['somerole']
};
const editedUser: User = {
name: '1212',
roles: ['somerole']
};
const action = new authActions.Fetching(userRequest);
const completion = new authActions.Success(editedUser);
actions = hot('-a', {a: action});
const response = cold('-a|', {a: userResponse});
rootService.login.and.returnValue(response);
const expected = cold('--b', {b: completion});
expect(effects.authUser$).toBeObservable(expected);
});
});
I tried make it according some example, but anything wrong.
You have to make a minor change to how you are setting the expect block in test. Try the following:
effects.authUser$.subscribe(actionSent => {
expect(actionSent).toBeObservable(expected)
})
instead of
expect(effects.authUser$).toBeObservable(expected);
I hope that will work for you.
seems like constructor break this. If I change effect code without constructor - its works
#Effect()
authUser$: Observable<any> = this.actions$.pipe(
ofType(authActions.FETCHING),
map((action: authActions.Fetching) => action.payload),
switchMap((paylod: UserRequest): any => this.rootService.login(paylod)
.pipe(
map((value: UserResponse) => {
const {sub, authorities} = value;
return new authActions.Success({
name: sub,
roles: authorities
});
}),
catchError(() => of(new authActions.Fail('wrong username or password')))
)
)
);
Related
I have a custom hook, which has structure of:
const urlHook = () => {
const router = useRouter();
const read = () => {
return validate(router.query.param);
}
const write = (params) => {
router.push(
{
query: {
param: params,
},
},
undefined,
{shallow: true},
)
}
const validate = (params) => {}
}
I want to test this hook using react-hooks-testing-library but I'm not sure how to setup for router.query.param to read values that I want or how to check if function write() will create correct url?
To mock entire hook - jest.requireActual:
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useParams: () => ({
blogId: 'company1',
articleId: 'blog1',
}),
useRouteMatch: () => ({ url: '/blog/blog1/article/article1' }),
}));
To mock history/routing state - MemoryRouter:
import {Route, MemoryRouter} from 'react-router-dom';
...
const renderWithRouter = ({children}) => (
render(
<MemoryRouter initialEntries={['blogs/1']}>
<Route path='blogs/:blogId'>
{children}
</Route>
</MemoryRouter>
)
)
Helpful example with explanations:
https://v5.reactrouter.com/web/guides/testing
I am trying to write the test cases for the below method :-
constructor(private dataSharing: DataSharingService) {
const res: any = this.dataSharing.getSystemUser();
this.systemUserData = res.source._value;
this.systemUserData.systemUserId && this.systemUserData.systemUserId === 1 ? this.disableButton = false : this.disableButton = true;
}
I have tried the below snippet in my spec.ts file for covering the above code :-
fdescribe('ManagePermissionsComponent', () => {
let component: ManagePermissionsComponent;
let fixture: ComponentFixture<ManagePermissionsComponent>;
let dataSharing = jasmine.createSpyObj('DataSharingService', ['getSystemUser']);
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [RouterTestingModule, HttpClientTestingModule],
declarations: [ManagePermissionsComponent],
providers: [
{provide: DataSharingService, dataSharing: roleServiceStub }, SessionStorageService]
})
.compileComponents();
});
beforeEach(() => {
component.systemUserData = dataSharing.getSystemUser.source._value
//component.systemUserData = {'username': 'akvishwakarma#netlink.com', 'firstName': 'Avani', 'lastName': 'Vishwakarma', 'systemUserId': 1, 'isActive': true, 'password' :"Avani123", 'oldPassword':'Avani123', 'email':'abhargav#gmail.com', 'contact':123, 'imageId':'jpg', 'isAccountLocked':true, 'accountLocked':'dds','accountLockedDate':null, 'loginAttempt':null, 'createdDate':null, 'createdBy':'aparna', 'updatedBy':'jdsjsd', 'updatedDate':null, 'otpGenerated':'dfdsf', 'otpGeneratedDate':'sdsd'};
fixture = TestBed.createComponent(ManagePermissionsComponent);
component = fixture.debugElement.componentInstance;
fixture.detectChanges();
});
But I am getting below error :-
I have added comments below, it should help you.
fdescribe('ManagePermissionsComponent', () => {
let component: ManagePermissionsComponent;
let fixture: ComponentFixture<ManagePermissionsComponent>;
// change this line to just a declaration like so
let dataSharing: jasmine.SpyObj<DataSharingService>;
beforeEach(async () => {
// move the assigning of the spy object here so you have a new
// spy object for every test (beforeEach)
dataSharing = jasmine.createSpyObj<DataSharingService>('DataSharingService', ['getSystemUser']);
await TestBed.configureTestingModule({
imports: [RouterTestingModule, HttpClientTestingModule],
declarations: [ManagePermissionsComponent],
providers: [
// this line was wrong as well, it should be useValue: dataSharing.
// every time the test requires DataSharingService, we provide the mock
{provide: DataSharingService, useValue: dataSharing }, SessionStorageService]
})
.compileComponents();
});
beforeEach(() => {
// need to mock getSystemUser before createComponent because
// we need it for the constructor.
// mock _value however you like
dataSharing.getSystemUser.and.returnValue({ source: { _value: {} }});
fixture = TestBed.createComponent(ManagePermissionsComponent);
component = fixture.debugElement.componentInstance;
fixture.detectChanges();
});
Following test case, will return the data correctly but JEST show as failed. The test is written using TestScheduler
Jest Result
expect(received).toEqual(expected) // deep equality
- Expected
+ Received
- Array [
- Object {
- "frame": 3,
- "notification": Notification {
- "error": undefined,
- "hasValue": true,
- "kind": "N",
- "value": Object {
- "type": "INITIALIZED",
- },
- },
- },
- ]
+ Array []
Code
import { ofType } from 'redux-observable';
import { mergeMap, map, tap } from 'rxjs/operators';
import { of, from } from 'rxjs';
import { TestScheduler } from 'rxjs/testing';
describe('routechange epic', () => {
const testScheduler = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
it('check apollo', () => {
const dependencies = {
apolloClient: {
mutate: ({ mutation, variables }: { mutation: any, variables: any }) =>
Promise.resolve({
data: { param: 'testA' }
})
},
};
const initializeOrg = (action$, state$, { apolloClient }) =>
action$
.pipe(
ofType('START'),
tap(act => console.log('---AAA', act)),
mergeMap(action =>
from(
apolloClient.mutate({
mutation: `something`,
variables: {
orgId: (action as any).params || ''
}
})
)
.pipe(
tap(x => console.log('----x', x)),
map(response => ({
type: 'INITIALIZED',
response,
}))
)
)
);
testScheduler.run(({ hot, cold, expectObservable }) => {
const action$ = hot('-a', {
a: { type: 'START', params: 'SomethingA' }
});
const state$ = null;
const output$ = initializeOrg(action$, state$, dependencies);
expectObservable(output$).toBe('---a', {
a: {
type: 'INITIALIZED'
}
})
});
});
});
We cannot use Promise.resolve as someone commented it.
It worked now with cold observable like below.
import { ofType } from 'redux-observable';
import { mergeMap, map, tap, toArray, take } from 'rxjs/operators';
import { of, from } from 'rxjs';
import { TestScheduler } from 'rxjs/testing';
describe('routechange epic', () => {
it('check apollo', async () => {
const testScheduler = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
const initializeOrg = (action$, state$, { apolloClient }) =>
action$
.pipe(
ofType('START'),
tap(act => console.log('---AAA', act)),
mergeMap(action =>
from(
apolloClient.mutate({
mutation: `something`,
variables: {
orgId: (action as any).params || ''
}
})
)
.pipe(
tap(x => console.log('----x', x)),
map(response => ({
type: 'INITIALIZED',
response,
}))
)
)
);
testScheduler.run(({ hot, cold, expectObservable }) => {
const action$ = hot('-a', {
a: { type: 'START', params: 'SomethingA' }
});
const state$ = null;
const dependencies = {
apolloClient: {
mutate: ({ mutation, variables }: { mutation: any, variables: any }) =>
cold('--a|', {
a: { data: { param: 'testA' } }
})
},
};
const output$ = initializeOrg(action$, state$, dependencies);
expectObservable(output$).toBe('---a', {
a: {
type: 'INITIALIZED',
response: {
data: { param: 'testA' }
}
}
})
});
});
});
I have been playing with rxjs and redux-observable for the last few days and have been struggle to find a way to a test for Observable.ajax. I have the following epic which create a request to https://jsonplaceholder.typicode.com/,
export function testApiEpic (action$) {
return action$.ofType(REQUEST)
.switchMap(action =>
Observable.ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
)
}
where,
export const REQUEST = 'my-app/testApi/REQUEST'
export const SUCCESS = 'my-app/testApi/SUCCESS'
export const FAILURE = 'my-app/testApi/FAILURE'
export const CLEAR = 'my-app/testApi/CLEAR'
export function requestTestApi () {
return { type: REQUEST }
}
export function successTestApi (response) {
return { type: SUCCESS, response }
}
export function failureTestApi (error) {
return { type: FAILURE, error }
}
export function clearTestApi () {
return { type: CLEAR }
}
The code works fine when runs in browser but not when testing with Jest.
I have try,
1) Create a test based on https://redux-observable.js.org/docs/recipes/WritingTests.html. The store.getActions() returns only { type: REQUEST }.
const epicMiddleware = createEpicMiddleware(testApiEpic)
const mockStore = configureMockStore([epicMiddleware])
describe.only('fetchUserEpic', () => {
let store
beforeEach(() => {
store = mockStore()
})
afterEach(() => {
epicMiddleware.replaceEpic(testApiEpic)
})
it('returns a response, () => {
store.dispatch({ type: REQUEST })
expect(store.getActions()).toEqual([
{ type: REQUEST },
{ type: SUCCESS, response }
])
})
})
2) Create a test based on Redux-observable: failed jest test for epic. It returns with
Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
it('returns a response', (done) => {
const action$ = ActionsObservable.of({ type: REQUEST })
const store = { getState: () => {} }
testApiEpic(action$, store)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
{ type: SUCCESS, response }
])
done()
})
})
Can someone point me out what is the correct way to test Observable.ajax ?
I would follow the second example, from StackOverflow. To make it work you'll need to make some minor adjustments. Instead of importing Observable.ajax in your epic file and using that reference directly, you need to use some form of dependency injection. One way is to provide it to the middleware when you create it.
import { ajax } from 'rxjs/observable/dom/ajax';
const epicMiddleware = createEpicMiddleware(rootEpic, {
dependencies: { ajax }
});
The object we passed as dependencies will be give to all epics as the third argument
export function testApiEpic (action$, store, { ajax }) {
return action$.ofType(REQUEST)
.switchMap(action =>
ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
);
}
Alternatively, you could not use the dependencies option of the middleware and instead just use default parameters:
export function testApiEpic (action$, store, ajax = Observable.ajax) {
return action$.ofType(REQUEST)
.switchMap(action =>
ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
);
}
Either one you choose, when we test the epic we can now call it directly and provide our own mock for it. Here are examples for success/error/cancel paths These are untested and might have issues, but should give you the general idea
it('handles success path', (done) => {
const action$ = ActionsObservable.of(requestTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.of({ url, method })
};
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
successTestApi({ url: '/whatever-it-is', method: 'WHATEVERITIS' })
])
done();
});
});
it('handles error path', (done) => {
const action$ = ActionsObservable.of(requestTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.throw({ url, method })
};
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
failureTestApi({ url: '/whatever-it-is', method: 'WHATEVERITIS' })
])
done();
});
});
it('supports cancellation', (done) => {
const action$ = ActionsObservable.of(requestTestApi(), clearTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.of({ url, method }).delay(100)
};
const onNext = chai.spy();
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe({
next: onNext,
complete: () => {
onNext.should.not.have.been.called();
done();
}
});
});
For the first way:
First, use isomorphic-fetch instead of Observable.ajax for nock support, like this
const fetchSomeData = (api: string, params: FetchDataParams) => {
const request = fetch(`${api}?${stringify(params)}`)
.then(res => res.json());
return Observable.from(request);
};
So my epic is:
const fetchDataEpic: Epic<GateAction, ImGateState> = action$ =>
action$
.ofType(FETCH_MODEL)
.mergeMap((action: FetchModel) =>
fetchDynamicData(action.url, action.params)
.map((payload: FetchedData) => fetchModelSucc(payload.data))
.catch(error => Observable.of(
fetchModelFail(error)
)));
Then, you may need an interval to decide when to finish the test.
describe("epics", () => {
let store: MockStore<{}>;
beforeEach(() => {
store = mockStore();
});
afterEach(() => {
nock.cleanAll();
epicMiddleware.replaceEpic(epic);
});
it("fetch data model succ", () => {
const payload = {
code: 0,
data: someData,
header: {},
msg: "ok"
};
const params = {
data1: 100,
data2: "4"
};
const mock = nock("https://test.com")
.get("/test")
.query(params)
.reply(200, payload);
const go = new Promise((resolve) => {
store.dispatch({
type: FETCH_MODEL,
url: "https://test.com/test",
params
});
let interval: number;
interval = window.setInterval(() => {
if (mock.isDone()) {
clearInterval(interval);
resolve(store.getActions());
}
}, 20);
});
return expect(go).resolves.toEqual([
{
type: FETCH_MODEL,
url: "https://test.com/assignment",
params
},
{
type: FETCH_MODEL_SUCC,
data: somData
}
]);
});
});
enjoy it :)
I want to unit test this file, can you please tell how to write jasmine test for this
import { Effect, Actions } from '#ngrx/effects';
import { NavigationActionType } from 'app/core/store/actions/navigation/navigation.action';
import { NavigationService } from 'app/core/services/navigation.service';
import 'rxjs/add/operator/switchMap';
#Injectable()
export class NavigationServiceEffect {
#Effect()
navigations$ = this.actions$
.ofType(NavigationActionType.TRIGGER_LOAD_NAVIGATION_ITEMS)
.switchMap( () => this.navigationService.load())
.map(data => ({ type: NavigationActionType.LOAD_NAVIGATIONS_LINKS, payload: data}));
constructor(private actions$: Actions, private navigationService: NavigationService) {}
}
it('should dispatch LOAD_NAVIGATIONS_LINKS', () => {
runner.queue({
type: NavigationActionType.TRIGGER_LOAD_NAVIGATION_ITEMS,
payload: 1
});
navigationServiceEffect.navigations$.subscribe(result => {
expect(result.type).toEqual(NavigationActionType.LOAD_NAVIGATIONS_LINKS)
expect(result.payload).toEqual(1);
});
}
Note you'll have to mock out the navigationService
You can use jasmine-marbles that provide hot and cold observables to test the effects like this:
it('should dispatch LOAD_NAVIGATIONS_LINKS action', () => {
// arrange
const action = new TriggerLoadNavigationItems();
const expected = cold('-a', {a: new LoadNavigationLinks({data: 'some test data'})});
spyOn(navigationService, 'load').and.returnValue({data: 'some test data'});
// act
actions$ = hot('-a', {a: action});
// assert
expect(effects.navigations$).toBeObservable(expected);
});