Stubbing vuex getters with sinonjs - unit-testing

On my application, inside a navigation guard used by my router, I have a vuex namespaced getter to check authentication state. The getter do the magic underlaying check if the user is authenticated.
I want to write a simple unit test which check that the redirection is done according to the authenticated state. I'm stucked on stubbing the getter.
My getter is the following :
isAuthenticated (state) {
return state.token !== null
}
My authentication module is the following :
export default {
namespaced: true,
state,
getters
}
And my store is the following :
export default new Vuex.Store({
modules: {
authentication
}
})
My naviguation guard is :
import store from '#/store'
export default (to, from, next) => {
if (store.getters['authentication/isAuthenticated']) {
next()
return
}
next({name: 'login'})
}
I've wrote that unit test :
describe('authenticated-guard.spec.js', () => {
let authenticatedStub
beforeEach(() => {
authenticatedStub = sandbox.stub(store.getters, 'authentication/isAuthenticated')
})
afterEach(() => {
sandbox.restore()
})
it('should redirect to login route when the user is not authenticated', () => {
// Given
const to = {}
const from = {}
const next = spy()
authenticatedStub.value(false)
// When
authenticatedGuard(to, from, next)
// Then
assert.ok(next.calledWith({name: 'login'}), 'should have redirected to login route')
})
})
The unit test trigger the following error : TypeError: Cannot redefine property: authentication/isAuthenticated.
I've tried as an alternative to stub using authenticatedStub.value(false) but the error is the same.
I'm unable to stub the getter to avoid to have store logics on guard tests.
Does someone beeing able to stub any getter outside of components ?
Regards

The problem is that vuex sets the getters as non-configurable properties, so they can't be changed.
A way to stub them is to stub the getters object itself so your test could work like this:
describe('authenticated-guard.spec.js', () => {
it('should redirect to', () => {
const authenticatedStub = sandbox.stub(store, 'getters')
// Given
const to = {}
const from = {}
const next = spy()
authenticatedStub.value({
'authentication/isAuthenticated': false
})
// When
authenticatedGuard(to, from, next)
// Then
expect(next.lastCall.args).to.deep.equal([{name: 'login'}], 'login route when the user is not authenticated')
authenticatedStub.value({
'authentication/isAuthenticated': true
})
authenticatedGuard(to, from, next)
expect(next.lastCall.args).to.deep.equal([], 'next route when the user is authenticated')
})
})

Related

Unit testing nestjs guards Unknown authentication strategy

Trying to write unit tests as described here but no idea how to get around this error
Exception has occurred: Error: Unknown authentication strategy "test-jwt"
at attempt (/home/user/Workspace/project/node_modules/passport/lib/middleware/authenticate.js:190:39)
at authenticate
auth file
import { Injectable } from "#nestjs/common";
import { AuthGuard } from "#nestjs/passport";
#Injectable()
export class MyGuard extends AuthGuard('test-jwt') { }
test
import { ExecutionContext } from "#nestjs/common";
import { MyGuard } from "./mygaurd";
it('test' () => {
const context: ExecutionContext = {
switchToHttp: () => context,
getRequest: () => {
return {
headers: {
authorization: `bearer ${jwt}`
}
}
},
getResponse: () => { }
} as unknown as ExecutionContext
const guard = new MyGuard()
expect(guard.canActivate(context)).toBeTrue();
})
The actual implementation works fine, I add it to a controller.
#UseGuards(MyGuard)
export class MyController {
I don't even need to add it as a provider or anything in my setup so not sure what other code to include.
I implemented a custom strategy which may be related
import { Strategy, ExtractJwt } from "passport-jwt";
import { Injectable } from "#nestjs/common";
import { PassportStrategy } from "#nestjs/passport";
#Injectable()
export class MyStrategy extends PassportStrategy(Strategy, 'test-jwt') {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'secret'
})
}
async validate(payload) {
...
}
}
And of course MyStrategy is added as a provider in my app.
I have unit tests covering my custom strategy already so it is really just the guard left
EDIT:
Trying Jay's suggestion below gets me a little closer but i'm still struggling.
It seems passport.use() expects a name and Strategy rather than a function (so TS compilation fails) so I tried
import passport, { Strategy } from "passport";
...
passport.use('test-jwt', {
authenticate: (payload) => true
} as Strategy);
and the error disappears, but the test now outputs
expect(received).toBeTrue()
Expected value to be true:
true
Received:
{}
Any further suggestions?
This is one of those quirky things with passport that I never thought I'd see. So, passport uses strategy names to determine what authentication method is actually being used, right? All of these strategies get registered to the passport context using passport.use(name, method) in the general scheme of things. In the context of Nest, this happens when you create a custom strategy, extend PassportStrategy and add the strategy as a provider as seen here. Later, the method passport.authenticate(strategy, (err, req, res, next) is called during the AuthGuard#canActivate method (the codes a bit complex, but this is where it happens). Because passport has never seen passport.use('test-jwt', authMethod) in the context of your test, it ends up not knowing what to do other than throwing the error about "Unknown authentication strategy".
Normally, the validate method is what becomes the authMethod, but if you're just needing this for the context of your test you can do something like
it('test' () => {
passport.use('test-jwt', (payload) => true);
const context: ExecutionContext = {
switchToHttp: () => context,
getRequest: () => {
return {
headers: {
authorization: `bearer ${jwt}`
}
}
},
getResponse: () => { }
} as unknown as ExecutionContext
const guard = new MyGuard()
expect(guard.canActivate(context)).toBeTrue();
})
and it should work out all right. You an then modify the value returned from that method, or make it a jest.fn() so that you can check what it was called with and modify what it returns if you need to do extra testing on the guard.

Im trying to mock a function from a service but Jest keeps calling the actual function instead of the mock function

I'm using Jest to test a function from a service that uses axios to make some api calls. The problem is that Jest keeps calling the actual services function instead of the mocked service function. Here is all of the code:
The tests:
// __tests__/NotificationService.spec.js
const mockService = require('../NotificationService').default;
beforeEach(() => {
jest.mock('../NotificationService');
});
describe('NotificationService.js', () => {
it('returns the bell property', async () => {
expect.assertions(1);
const data = await mockService.fetchNotifications();
console.log(data);
expect(data).toHaveProperty('data.bell');
});
});
The mock:
// __mocks__/NotificationService.js
const notifData = {
bell: false,
rollups: [
{
id: 'hidden',
modifiedAt: 123,
read: true,
type: 'PLAYLIST_SUBSCRIBED',
visited: false,
muted: false,
count: 3,
user: {
id: 'hidden',
name: 'hidden'
},
reference: {
id: 'hidden',
title: 'hidden',
url: ''
}
}
],
system: [],
total: 1
};
export default function fetchNotifications(isResolved) {
return new Promise((resolve, reject) => {
process.nextTick(() =>
isResolved ? resolve(notifData) : reject({ error: 'It threw an error' })
);
});
}
The service:
import axios from 'axios';
// hardcoded user guid
export const userId = 'hidden';
// axios instance with hardcoded url and auth header
export const instance = axios.create({
baseURL: 'hidden',
headers: {
Authorization:
'JWT ey'
}
});
/**
* Notification Service
* Call these methods from the Notification Vuex Module
*/
export default class NotificationService {
/**
* #GET Gets a list of Notifications for a User
* #returns {AxiosPromise<any>}
* #param query
*/
static async fetchNotifications(query) {
try {
const res = await instance.get(`/rollups/user/${userId}`, {
query: query
});
return res;
} catch (error) {
console.error(error);
}
}
}
I've tried a couple of variations of using require instead of importing the NotificationService, but it gave some other cryptic errors...
I feel like I'm missing something simple.
Help me please :)
The problem is that Jest keeps calling the actual services function instead of the mocked service function.
babel-jest hoists jest.mock calls so that they run before everything else (even import calls), but the hoisting is local to the code block as described in issue 2582.
I feel like I'm missing something simple.
Move your jest.mock call outside the beforeEach and it will be hoisted to the top of your entire test so your mock is returned by require:
const mockService = require('../NotificationService').default; // mockService is your mock...
jest.mock('../NotificationService'); // ...because this runs first
describe('NotificationService.js', () => {
it('returns the bell property', async () => {
...
});
});

How to disable type checking for test file in Visual Code Studio

I am currently write some tests for my angular application. When I mock class to check if method is correctly called, I make a mock implementation. When I do that my code is underlined with red dots because my mock does not respect the true implementation of the type.
Here is an example. Here I want mock HttpLink class in order to test that create function is called. I dont mind how HttpLink object is construct. So I mocked HttpLink class and I mock create function. But visual studio code put red dot when I call the constructor because it does not respect the true HttpLink implementation :
import { Apollo } from 'apollo-angular'
jest.mock('apollo-angular', () => {
return {
Apollo: () => {
return {
create: jest.fn()
}
}
}
})
import { HttpLink } from 'apollo-angular-link-http'
jest.mock('apollo-angular-link-http', () => {
return {
HttpLink: () => {
return {
create: jest.fn()
}
}
}
})
import { GraphqlService } from './graphql.service'
describe('GraphqlService', () => {
let apollo = new Apollo()
let httpLink = new HttpLink() // <====== This is red because type checking see this class need one argument but I mocked the constructor.
let graphqlService
beforeAll(() => {
graphqlService = new GraphqlService(apollo, httpLink)
})
it('should be created', () => {
expect(graphqlService).toBeTruthy()
})
it('should create apollo client correctly', () => {
expect(apollo.create).toHaveBeenCalled()
})
})
Is there a way to desactivate type checking that visual studio code does, but only for test files ?
You can go to the preferences and then Settings
In User Settings, you have to update following :
"typescript.validate.enable": true,
to
"typescript.validate.enable": false,
Hope it will help you !!

How to test react component correctly?

Recently I am learning to test React with jest and enzyme, It seems hard to understand what a unit test is it, my code
import React from "react";
class App extends React.Component {
constructor() {
super();
this.state = {
value: ""
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const value = e.target.value;
this.setState({
value
});
}
render() {
return <Nest value={this.state.value} handleChange={this.handleChange} />;
}
}
export const Nest = props => {
return <input value={props.value} onChange={props.handleChange} />;
};
export default App;
and my test
import React from "react";
import App, { Nest } from "./nest";
import { shallow, mount } from "enzyme";
it("should be goood", () => {
const handleChange = jest.fn();
const wrapper = mount(<App />);
wrapper.find("input").simulate("change", { target: { value: "test" } });
expect(handleChange).toHaveBeenCalledTimes(1);
});
IMO, the mocked handleClick will intercept the handleClick on App,
if this is totally wrong, what's the right way to use mock fn and test the handleClick be called.
Another: I search a lot, read the similar situations, seem like this iscontra-Unit Test,
Probably I should test the two component separately, I can test both components,
test the
<Nest value={value} handleChange={handleChange} />
by pass the props manually, and then handleChangeinvoked by simulate change
it passed test.
but how can I test the connection between the two?
I read
some work is React Team's Work
...
I don't know which parts I have to test in this case, and Which parts react already tested and don't need me to test. That's confusing.
You should take the path of testing the Nest component in isolation first, passing your mocked handleChange as a prop, to verify that input changes are being propagated.
If you want to test the state part, then you can get the instance of your App class from enzyme and call that method directly:
it("should update the Nest value prop when change is received", () => {
const wrapper = mount(<App />);
const instance = wrapper.instance()
instance.handleChange( { target: { value: "test" } })
const nestComponent = wrapper.find("Nest").first()
expect(nestComponent).prop('value').toEqual('test');
});
This a very very basic, almost not needed to test piece of code, but it will get your test coverage up if that's what you're after.
Doc for instance: http://airbnb.io/enzyme/docs/api/ReactWrapper/instance.html
If you want to test for the connection. From what I see, the nest component is a child component inside the App component. You could test that <App /> contains `.
describe('<App />', () => {
it('should contain a nest component', () => {
const wrapper = mount(<App />);
expect(wrapper.find(<Nest />)).toHaveLength(1);
});
});
Secondly, since the onChange event on the nest component updates the state in the App component, you can also test for state changes since its a behavior you expect.
it('should update state', () => {
//find input and simulate change with say {value: 'new value'} and then
expect(wrapper.state().value).toBe('newValue');
});
I hope this helps.

Mocking RouterStateSnapshot in Jasmine testing

Although I have been writing Angular 2 for a while now, I am only just writing my first Jasmine tests and have run into a little difficulty. I am trying to test that the CanActivate method of service implementing CanActivate is behaving itself, and is returning true or false as expected.
My method looks like this:
canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean> {
return this.store$
.map( ( store: StoreState ) => store.currentUser )
.first()
.map( ( user ) => {
if ( user.isAuthenticated ) {
return true;
}
// TODO: This needs refactoring. Need to provide RouterStateSnapshot in test,
// rather than ignoring it!
this.redirectUrl = state ? state.url : '';
this.injector.get( Router ).navigate( ['/login'] );
return false;
} );
}
An extract of my test looks like this:
service = TestBed.get( AuthGuardService );
it( 'should prevent navigation', () => {
service.canActivate(null, null).subscribe((res) => expect( res ).toBeTruthy() );
} );
How do I mock/stub/whatever the second parameter of my call to service.canActivate, rather than simply passing in null?
describe('AuthGuard', () => {
let mockSnapshot: RouterStateSnapshot;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
// so we can get the Router injected
RouterTestingModule,
// other imports as needed
],
// usual config here
});
// create a jasmine spy object, of the required type
// toString is because we have to mock at least one method
mockSnapshot = createSpyObj<RouterStateSnapshot>('RouterStateSnapshot', ['toString']);
});
it('should prevent non-authenticated access',
async(inject([AuthGuard, AuthService, Router], (guard: AuthGuard, auth: AuthService, router: Router) => {
// ensure we're logged out
auth.logout();
// set the url on our mock snapshot
mockSnapshot.url = '/protected';
// so we can spy on what's been called on the router object navigate method
spyOn(router, 'navigate');
expect(guard.canActivate(null, mockSnapshot)).toBeFalsy();
// check that our guard re-directed the user to another url
expect(router.navigate).toHaveBeenCalled();
})));
});
})
Here is my solution which I used for unit testing of Custom Router State Serializer
custom-serializer.ts
import { RouterStateSerializer } from '#ngrx/router-store';
import { RouterStateSnapshot, Params } from '#angular/router';
/**
* The RouterStateSerializer takes the current RouterStateSnapshot
* and returns any pertinent information needed. The snapshot contains
* all information about the state of the router at the given point in time.
* The entire snapshot is complex and not always needed. In this case, you only
* need the URL and query parameters from the snapshot in the store. Other items could be
* returned such as route parameters and static route data.
*/
export interface RouterStateUrl {
url: string;
params: Params;
queryParams: Params;
}
export class CustomRouterStateSerializer
implements RouterStateSerializer<RouterStateUrl> {
serialize(routerState: RouterStateSnapshot): RouterStateUrl {
let route = routerState.root;
while (route.firstChild) {
route = route.firstChild;
}
const { url, root: { queryParams } } = routerState;
const { params } = route;
// Only return an object including the URL, params and query params
// instead of the entire snapshot
return { url, params, queryParams };
}
}
custom-serializer.spec.ts
import { CustomRouterStateSerializer } from './utils';
import { RouterStateSnapshot } from '#angular/router';
describe('Utils CustomRouterStateSerializer', () => {
let mockSnapshot: RouterStateSnapshot;
let serializer: CustomRouterStateSerializer;
let mockSnapshotProxy;
beforeEach(() => {
mockSnapshot = jasmine.createSpyObj<RouterStateSnapshot>('RouterStateSnapshot', ['toString']);
serializer = new CustomRouterStateSerializer();
});
it('should serialize RouterStateSnapshot to subset of params', () => {
mockSnapshotProxy = new Proxy(mockSnapshot, {
get(target, prop) {
if (prop === 'root') {
return {
params: {
id: 100
},
queryParams: {
name: 'John'
}
};
} else if (prop === 'url') {
return '/orders';
}
},
});
const result = serializer.serialize(mockSnapshotProxy);
expect(result.url).toBe('/orders');
expect(result.params.id).toBe(100);
expect(result.queryParams.name).toBe('John');
});
});
I used jasmine.createSpyObj to create object with proper type and Proxy to pass in required properties