How to mock AWS Amplify in Jest? - unit-testing

I am creating an application with Vue and using Vue Test Utils and Jest as a unit testing framework. However, I encountered an issue in testing the scenario when it should show invalid credentials on failed login. I was wondering how to mock the Auth of AWS Amplify. I am not quite sure if I am doing the testing right because I am new to unit testing in frontend.
Login.vue:
import { Auth } from 'aws-amplify'
import { required } from 'vuelidate/lib/validators'
export default {
name: 'loginComponent',
data() {
return {
form: {
email: null,
password: null,
},
authErrMsg: '',
isShowAuthErr: false,
isLoading: false,
}
},
validations: {
form: {
email: { required },
password: { required }
}
},
methods: {
validateState(name) {
const { $dirty, $error } = this.$v.form[name];
return $dirty ? !$error : null;
},
onSubmit() {
this.$v.$touch();
if (this.$v.$invalid) {
return
}
this.isLoading = true
this.isShowAuthErr = false
const { email, password } = this.form
Auth.signIn(email, password).then(() => {
this.isLoading = false
this.$store.dispatch('ACTION_SET_LOGGEDIN_STATUS', true)
this.$router.push({ name: 'home' })
}).catch(() => {
this.isLoading = false
this.authErrMsg = 'Invalid login credentials'
this.$store.dispatch('ACTION_SET_LOGGEDIN_STATUS', false)
this.isShowAuthErr = true
})
}
}
}
Login.spec.js:
import { mount, shallowMount, createLocalVue } from '#vue/test-utils'
import Login from '#/components/Login'
import BootstrapVue from 'bootstrap-vue'
import Vuelidate from 'vuelidate'
import Vuex from 'vuex'
import Auth from '#aws-amplify/auth'
import flushPromises from 'flush-promises'
const localVue = createLocalVue()
localVue.use(BootstrapVue)
localVue.use(Vuelidate)
localVue.use(Vuex)
localVue.use(Auth)
let wrapper
beforeEach(() => {
wrapper = mount(Login, { localVue,
form: {
email: null,
password: null,
}
})
})
describe('Login', () => {
it('should error message on failed login', async () => {
wrapper.find('input[name="email"]').setValue('email#gmail.com')
wrapper.find('input[name="password"]').setValue('123ABC')
wrapper.find("form").trigger("submit.prevent")
await flushPromises()
Auth.signIn = jest.fn().mockImplementation(() => {
throw new Error('Incorrect username or password.')
});
expect(Auth.signIn()).rejects.toThrow()
})
})
Error I got:
TypeError: Cannot read property 'clientMetadata' of undefined

Related

How to mock an inject repository with Jest and NestJS?

In a NestJS project using Jest test the service case. I created a find method and use it in a resolver(GraphQL) function. When test the resolver, it can't find the find method in the service.
src/serivce/post.ts
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { AbstractPostRepository } from '#abstractRepository/AbstractPostRepository';
import { PostRepository } from '#repository/PostRepository';
#Injectable()
export class PostService {
constructor(
#InjectRepository(PostRepository)
private postRepo: AbstractPostRepository,
) {}
async findById(id: string) {
const posts = await this.postRepo.findById(id);
......
return posts;
}
}
src/resolver/query/post.ts
import { Args, Query, Resolver } from '#nestjs/graphql';
import { Authorized } from '#lib/auth/authorized';
import { PermissionScope } from '#lib/auth/permissionScope';
import { PostService } from '#service/postService';
#Resolver()
export class PostsResolver {
constructor(private postService: PostService) {}
#PermissionScope()
#Authorized([
PermissionEnum.READ_MANAGEMENT,
])
#Query()
async findPosts() {
return await this.postService.findById(id);
}
}
The unit test for a resolver related this service:
test/unit/resolver/post.spec.ts
import { Test, TestingModule } from '#nestjs/testing';
import { PERMISSION_MANAGER } from '#lib/auth/permissionManager.module';
import { PostsResolver } from '#resolver/query/post';
import { PostService } from '#service/postService';
describe('PostsResolver', () => {
let resolver: PostsResolver;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
PostsResolver,
{
provide: PERMISSION_MANAGER,
useValue: jest.fn(),
},
{
provide: PostService,
useFactory: () => ({
findById: jest.fn(() => [
{
id: '11111111-1111-1111-1111-111111111111',
name: 'name1',
},
]),
}),
},
],
}).compile();
resolver = module.get<PostsResolver>(PostsResolver);
});
describe('findPosts', () => {
it('should return data', async () => {
const posts = await resolver.findPosts({
id: '11111111-1111-1111-1111-111111111111',
});
const expected = [
{
id: '11111111-1111-1111-1111-111111111111',
name: 'name1',
},
];
expect(posts).toEqual(expected);
});
});
});
When run this test got error:
● PostsResolver › findPosts › should return data
TypeError: Cannot read properties of undefined (reading 'findById')
22 | { id }: FindPostsInput,
23 | ) {
> 24 | return await this.postService.findById(
| ^
25 | id,
26 | );
at PostsResolver.findPosts (src/resolver/query/post.ts:24:41)
at Object.<anonymous> (test/unit/resolver/post.spec.ts:59:42)
It seems the mock service findById in the Test.createTestingModule doesn't work. How to mock correctly? Is it related some inject reasons?

hapi authentication strategy karma test with sinon with async function

I am trying to test the authentication scheme with hapi server. I have two helper function within the same file where I put my authentication scheme. I want to test when this successfully authenticate the user. But in my test case I always get 401 which is the unauthenicated message.
export const hasLegitItemUser = async (request, email, id) => {
const {
status,
payload: {users}
} = await svc.getRel(request, email);
if (status !== STATUS.OK) {
return false;
}
return users.includes(user)
};
export const getUser = async request => {
const token = request.state._token;
const res = await svc.validateToken({request, token});
const {
userInfo: {email}
} = res;
const id = extractId(request.path);
const isLetgitUser = await hasLegitItemUser(
request,
email,
id
);
res.isLegitUser = isLegitUser;
return res;
};
const scheme = (server, options) => {
server.state("my_sso", options.cookie);
server.ext("onPostAuth", (request, h) => {
return h.continue;
});
return {
async authenticate(request, h) {
try {
const {
tokenValid,
isLegitUser,
userInfo
} = await getUser(request);
if (tokenValid && isLegitUser) {
request.state["SSO"] = {
TOKEN: request.state._token
};
return h.authenticated({
credentials: {
userInfo
}
});
} else {
throw Boom.unauthorized(null,"my_auth");
}
} catch (err) {
throw Boom.unauthorized(null, "my_auth");
}
}
};
};
My Test file:
import Hapi from "hapi";
import sinon from "sinon";
import auth, * as authHelpers from "server/auth";
import {expect} from "chai";
import pcSvc from "server/plugins/services/pc-svc";
describe("Authentication Plugin", () => {
const sandbox = sinon.createSandbox();
const server = new Hapi.Server();
const authHandler = request => ({
credentials: request.auth.credentials,
artifacts: "boom"
});
before(() => {
server.register({
plugin: auth,
});
const route = ["/mypage/{id}/home"];
route.forEach(path => {
server.route({
method: "GET",
path,
options: {
auth: auth,
handler:{}
}
});
});
});
afterEach(() => {
sandbox.restore();
});
it("should authorize user if it is a validated user", async () => {
sandbox
.stub(authHelpers, "getUser")
.withArgs(request)
.resolves({
tokenValid: true,
isLegitUser: true,
userInfo: {}
});
return server
.inject({
method: "GET",
url:
"/mypage/888/home"
})
.then(res => {
expect(res.statusCode).to.equal(200);
expect(res.result).to.eql({
userInfo: {
email: "abc#gmail.com",
rlUserId: "abc",
userId: "abc#gmail.com"
}
});
});
});
});
I always get the 401 error for unauthenticated. It seems like my "getUser" function in my test is not triggering for some reason, it goes straight to the throw statement in the catch phase in my code. Please help.

Vue Jest - Create Mock Api server

I want to create a Mock API Server for my Jest tests so that I can define all my backend endpoints and create responses and authentication checks.
I have managed to set up the server and routes by following some of the source code from Chris Fritz "Vue-Enterprice-boilerplate":
https://github.com/chrisvfritz/vue-enterprise-boilerplate/tree/master/tests/unit
// jest.config.js
const _ = require("lodash");
process.env.MOCK_API_PORT = process.env.MOCK_API_PORT || _.random(9000, 9999);
module.exports = {
preset: "#vue/cli-plugin-unit-jest",
setupFiles: ["./tests/unit/setup"],
globalSetup: "<rootDir>/tests/unit/global-setup",
globalTeardown: "<rootDir>/tests/unit/global-teardown",
testMatch: ["**/(*.)spec.js"],
moduleFileExtensions: ["js", "jsx", "json", "vue"],
transform: {
"^.+\\.vue$": "vue-jest",
"^.+\\.js$": "babel-jest",
".+\\.(css|scss|jpe?g|png|gif|webp|svg|mp4|webm|ogg|mp3|wav|flac|aac|woff2?|eot|ttf|otf)$":
"jest-transform-stub"
},
transformIgnorePatterns: ["/node_modules/(?!vue-spinner)"],
testURL: process.env.API_BASE_URL || `http://localhost:${process.env.MOCK_API_PORT}`
};
The server runs when the tests starts and I can console log the route files.
I just don't know how the axios call from my Vuex would go with the mock API instead of the real one.
Might need to import axios somewhere in the test to prevent the development URL to be used?
/tests/mock-api/routes/auth.js
const Users = require("../resources/users");
module.exports = app => {
console.log('I can see this during tests!');
app.post("/api/v1/login", async (req, res) => {
console.log("I don't see this..");
await Users.authenticate(req.body)
.then(user => {
res.json(user);
})
.catch(error => {
res.status(401).json({ message: error.message });
});
});
});
// /views/Login.spec.js
import Vue from "vue";
import Vuelidate from "vuelidate";
import Login from "#/views/Login";
import BaseButton from "#/components/Globals/_base-button.vue";
import BaseInput from "#/components/Globals/_base-input.vue";
import BaseLabel from "#/components/Globals/_base-label.vue";
import flushPromises from "flush-promises";
import store from "#/store";
import { shallowMount } from "#vue/test-utils";
Vue.use(Vuelidate);
describe("#/views/Login", () => {
// other tests..
it("redirects to posts on successful login", async () => {
const wrapper = shallowMount(Login, { store, stubs: { BaseInput, BaseButton, BaseLabel } });
wrapper.vm.$v.$touch();
const spyDispatch = jest.spyOn(wrapper.vm.$store, "dispatch");
const username = wrapper.find("#username");
const password = wrapper.find("#password");
username.element.value = "johndoe#email.com";
password.element.value = "passwordz";
username.trigger("input");
password.trigger("input");
await wrapper.find("#submitBtn").trigger("click.prevent");
await wrapper.vm.$nextTick();
await flushPromises();
await expect(spyDispatch).toHaveBeenCalledWith("auth/login", {
username: username.element.value,
password: password.element.value
});
// #TODO add expect for redirect as well
});
// /store/auth.js (vuex)
export const actions = {
async login({ commit }, { username, password }) {
console.log("I see this");
const response = await axios.post("/login",
{ username, password }, { withCredentials: true });
console.log("I don't see this");
// #TODO error handling
if (!response) return;
commit("setUser", { ...response.data.user });
router.push({ name: "Posts" });
},
The login action gets called but I don't get passed the axios.post.
Do I need to import axios somewhere to make sure I get a fresh instance? (Vuex uses one I set the baseURL and headers)
All the other tests and logic works except this.

how to unit test Angularfire2(version 5) auth service with google provider login

I'm trying to set up unit tests for a sample Angular5 app using AngularFire2 (version5) google provider login, My auth service is fairly simple and it looks like this:
let authState = null;
let mockAngularFireAuth: any = {authState: Observable.of(authState)};
#Injectable()
export class AuthService {
loggedIn: boolean;
private user: Observable<firebase.User>;
constructor(
public afAuth: AngularFireAuth
) {
this.user = afAuth.authState;
this.user.subscribe(
(user) => {
if (user) {
this.loggedIn = true;
} else {
this.loggedIn = false;
}
});
}
// --------------------------------- Google Login -----------------------------------
loginWithGoogle() {
// Sign in/up with google provider
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION)
.then(() => {
this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider())
.catch((error) => {
if (error.code === 'auth/account-exists-with-different-credential') {
alert('This email address is already registered');
}
});
});
}
// ------------------------- Checks User Authentication -----------------------
isAuthenticated() {
// returns true if the user is logged in
return this.loggedIn;
}
// --------------------------------- User LogOut -----------------------------------
logOut() {
this.afAuth.auth.signOut()
.then(() => {
this.loggedIn = false;
});
}
}
I want to test my loginWithGoogle() method but I am not sure where to start. So far my auth service spec file looks like this:
describe('AuthService', () => {
let authService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
AngularFireDatabaseModule,
AngularFireModule.initializeApp(environment.firebase),
RouterTestingModule
],
providers: [
{provide: AngularFireAuth, useValue: mockAngularFireAuth},
AuthService,
]
});
inject([AuthService], (service: AuthService) => {
authService = service;
})();
});
it('should be defined', () => {
expect(authService).toBeDefined();
});
it('should return true if loggedIn is true', () => {
expect(authService.isAuthenticated()).toBeFalsy();
authService.loggedIn = true;
expect(authService.isAuthenticated()).toBeTruthy();
});
});
Any help would be appreciated.
Well, this is what I did. I mocked the AngularFireAuth and returned the promise with reject or resolve promise to be caught. I am new to jasmine and testing, so feel free to correct me if I am doing something wrong.
it('should return a rejected promise', () => {
authState = {
email: 'lanchanagupta#gmail.com',
password: 'password',
};
mockAngularFireAuth = {
auth: jasmine.createSpyObj('auth', {
'signInWithPopup': Promise.reject({
code: 'auth/account-exists-with-different-credential'
}),
}),
authState: Observable.of(authState)
};
mockAngularFireAuth.auth.signInWithPopup()
.catch((error: { code: string }) => {
expect(error.code).toBe('auth/account-exists-with-different-credential');
});
});
it('should return a resolved promise', () => {
authState = {
email: 'lanchanagupta#gmail.com',
password: 'password',
uid: 'nuDdbfbhTwgkF5C6HN5DWDflpA83'
};
mockAngularFireAuth = {
auth: jasmine.createSpyObj('auth', {
'signInWithPopup': Promise.resolve({
user: authState
}),
})
};
mockAngularFireAuth.auth.signInWithPopup()
.then(data => {
expect(data['user']).toBe(authState);
});
});

vue.js unit test w sinon, how to stub a method?

I would like to stub this method ( this.login(°. ) in my submit method of a component, but I get an error :
✗ calls store action login when the form is submitted
TypeError: Cannot stub non-existent own property login
here is the method :
methods: _.extend({}, mapActions(['login']), {
clearErrorMessage () {
this.hasError = false
},
submit () {
return this.login({user: { email: this.email, password: this.password }})
.then((logged) => {
if (logged) {
this.$router.push('shoppinglists')
} else {
this.hasError = true
}
})
}
}),
I tried the following
sandbox.stub(LoginPage, 'login').withArgs(payload).returns(Promise.resolve(response))
in my spec
describe('LoginPage.vue', () => {
let actions
let getters
let store
var sandbox, payload, response
beforeEach(() => {
sandbox = sinon.sandbox.create()
...
})
afterEach(() => {
sandbox.restore()
})
it('calls store action login when the form is submitted', () => {
payload = {user: {email: 'john.doe#domain.com', password: 'john123'}}
response = true
sandbox.stub(LoginPage, 'login').withArgs(payload).returns(Promise.resolve(response))
const wrapper = mount(LoginPage, { store })
const form = wrapper.find('form')[0]
form.trigger('submit')
expect(actions.login.calledOnce).to.equal(true)
})
just stubbing the action with a Promise resolving the status ( true / false
import LoginPage from '#/pages/LoginPage'
import Vue from 'vue'
import Vuex from 'vuex'
import sinon from 'sinon'
import { mount } from 'avoriaz'
Vue.use(Vuex)
describe('LoginPage.vue', () => {
let actions
let store
let sandbox
beforeEach(() => {
sandbox = sinon.sandbox.create()
})
afterEach(() => {
sandbox.restore()
})
it('calls store action login when the form is submitted, w good credentials', (done) => {
actions = {
login: sandbox.stub().returns(Promise.resolve(true))
}
store = new Vuex.Store({
actions
})
const wrapper = mount(LoginPage, { store })
const form = wrapper.find('form')[0]
form.trigger('submit')
wrapper.vm.$nextTick(() => {
expect(actions.login.calledOnce).to.equal(true)
expect(wrapper.data().hasError).to.equal(false)
done()
})
})
it('calls store action login when the form is submitted, w wrong credentials', (done) => {
actions = {
login: sandbox.stub().returns(Promise.resolve(false))
}
store = new Vuex.Store({
actions
})
const wrapper = mount(LoginPage, { store })
const form = wrapper.find('form')[0]
form.trigger('submit')
wrapper.vm.$nextTick(() => {
expect(actions.login.calledOnce).to.equal(true)
expect(wrapper.data().hasError).to.equal(true)
done()
})
})
})