getting Error: Expected spy onSubmit to have been called - karma-jasmine

I am new in angular and trying to write unit testing in angular 5 using karma-jasmine.
i have a login page which has 2 parameter username and password, and onSubmit() is function which is used to call api to authenticate user. check below file for login component.
login.component.ts
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
myform: FormGroup;
username: FormControl;
password: FormControl;
errMsg: string;
loginErr: boolean;
model: any = {};
constructor(
private http: Http,
private router: Router
) {
}
ngOnInit() {
this.createFormControls();
this.createForm();
}
createFormControls() {
this.username = new FormControl('', Validators.required);
this.password = new FormControl('', [
Validators.required,
Validators.minLength(6)
]);
}
createForm() {
this.myform = new FormGroup({
username: this.username,
password: this.password
});
enter code here
}
onSubmit() {
this.errMsg = "";
if (this.myform.valid) {
var data = {
'Username': this.username.value,
'Password': this.password.value
};
var options = {
type: "POST",
url: GlobalVariable.BASE_API_URL + "authentication/authenticate-user",
content: "application/json; charset=utf-8",
contentType: 'application/json',
async: false,
processing: true,
crossDomain: true,
xhrFields: { withCredentials: true },
body: JSON.stringify(data),
};
let headers = new Headers({ 'Content-Type': 'application/json' });
this.http.post(options.url, options.body, { headers: headers }).subscribe((data) => {
console.log(JSON.parse(data['_body']));
}, (err) => {
console.log(data['_body']);
this.errMsg = "Invalid Login Attempt.";
}, () => {
console.log("All Good With The Data")
});
}
else
{
}
}
}
Now i am trying to write unit test for above file, check below spec.ts file for sample code
****login.component.spec.ts****
describe('LoginComponent', () => {
let component//: LoginComponent;
let fixture//: ComponentFixture<LoginComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginComponent],
imports: [ReactiveFormsModule, HttpModule, AppRoutingModule, RouterModule, FormsModule],
providers: [
{provide: Globals, useValue: GlobalVariable.BASE_API_URL},
{provide: APP_BASE_HREF, useValue: '/'}
],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create login component', () => {
spyOn(component,'onSubmit').and.callThrough();
component.username = 'username';
component.password = '12345678';
expect(component.onSubmit).toHaveBeenCalled();
});
});
when I have run this unit test it shows error as image attached, please suggest me what mistake is in my code. how I can write a unit test to authenticate user by passing username and password and call the onSubmit function.

I was facing this issue in one of my component's test in Angular 6 application. What I did is, I moved my spyOn section before the component section. After that, my tests were running fine.
Before
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [HomeComponent, SingleNewsComponent, NewsComponent, DummyComponent],
imports: [MatCardModule, MatSelectModule, MatButtonModule, HttpModule, NewsRouterModule, BrowserAnimationsModule],
providers: [
{ provide: APP_BASE_HREF, useValue: '/' },
ApiService]
})
.compileComponents();
spyOn(apiService, 'post').and.returnValue(new Observable<any>());
fixture = TestBed.createComponent(SingleNewsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
apiService = TestBed.get(ApiService);
}));
After
.compileComponents();
apiService = TestBed.get(ApiService);
spyOn(apiService, 'post').and.returnValue(new Observable<any>());
fixture = TestBed.createComponent(SingleNewsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
As you can see, I have changed the order of the service spy. Thanks to Supamiu for putting some light on this here. Hope it helps.

Related

Nuxt with Sanctum : $auth and cookie deleted after refresh

I have a problem with the authenticate with my Nuxt application (static SSR).
I'm using Sanctum. But the $auth variable is emptied after a refresh. So the user is disconnected.
login.vue
await axios.get(`${process.env.apiUrl}/sanctum/csrf-cookie`)
const res = await this.$auth.loginWith('laravelSanctum', {
data: {
email: this.userLogin.login.email,
password: this.userLogin.login.password,
}
})
.catch((error) => {
this.userLogin.messageError = 'Wrong credentials'
[ ... stop process code ...]
})
await this.$auth.$storage.setUniversal('_auth.user', JSON.stringify(res.data.user))
await this.$auth.setUser(res.data.user)
store/index.js
export const actions = {
async nuxtServerInit({ commit, dispatch }) {
const user = this.$auth.$storage.getUniversal('_auth.user')
if (user) {
await this.$auth.setUser(user)
}
}
}
nuxt.config.js
auth: {
strategies: {
laravelSanctum: {
provider: 'laravel/sanctum',
url: process.env.apiUrl,
endpoints: {
csrf: { url: '/sanctum/csrf-cookie', methods: 'GET' },
login: { url: '/api/login', method: 'POST' },
logout: { url: '/api/logout', method: 'POST' },
user: false
},
user: {
property: false,
autoFetch: false
},
cookie: false
}
},
redirect: {
login: '/mon-compte/login',
logout: '/mon-compte/login',
//home: '/mon-compte/mon-espace',
register: '/mon-compte/register'
}
}

Jest + Vue test utlis TypeError: Cannot set property 'content' of null [duplicate]

This question already has an answer here:
Test suite failed to run TypeError: Cannot set property 'content' of null Running in Jest
(1 answer)
Closed 1 year ago.
I am writing a test on a Vue project, and I am new to Framwork Jest and Vue testing utilities, have not found solutions to similar issues, have tried with multiple components but the error is still similar, I thought about Mock axios, it didn't work, if someone can help me I'm stuck,
if someone can offer me a solution or have an idea how it goes, it will be perfect
Api.js
import axios from 'axios'
import qs from 'qs';
import router from '#/router';
import defaultExport from '#/store';
let apiBaseUrl = document.querySelector('meta[name="apiBaseUrl"]');
if(!apiBaseUrl) {
apiBaseUrl.content = '/api/';
}
let newAxios = axios.create({
headers: {
// A fix for IE11 - we need to define Pragma header
Pragma: 'no-cache',
// 'X-Requested-With': 'XMLHttpRequest'
},
withCredentials: true,
//baseURL: apiBaseUrl.content,
paramsSerializer: function (params) {
return qs.stringify(params)
}
});
Login.vue
<script>
****
data() {
return {
currentMode: "login",
passwordForgotMode: false,
registerMode: false,
email: "",
password: "",
rememberMe: false,
emailRules: [
v => !!v || 'E-Mail wird benötigt',
],
passwordRules: [
v => !!v || 'Passwort wird benötigt',
],
valid: false,
}
},
computed: {
...mapGetters({
isAdministrator: 'account/isAdministrator',
})
},
methods: {
...mapActions({
handleLogin: 'account/handleLogin',
addSnackbarFromError: 'app/addSnackbarFromError',
}),
send() {
if (this.$refs.form.validate()) {
this.handleLogin({
rememberMe: this.rememberMe,
email: this.email,
password: this.password,
})
.then(() => {
window.localStorage.setItem('logged_in', true);
if (this.$route.query.redirect) {
this.$router.push(decodeURIComponent(this.$route.query.redirect));
} else {
if (this.$store.getters["account/isAdministrator"]) {
this.$router.push({name: 'userNotificationsOverview'});
} else {
this.$router.push({name: 'startingSite'});
}
}
})
.catch((error) => {
this.password = '';
this.addSnackbarFromError(error)
})
}
}
}
}
</script>
Test.test.js
import { shallowMount, createLocalVue } from '#vue/test-utils';
import Vuex from 'vuex';
import Login from "#pages/Login";
let wrapper;
let store;
let actions;
let mutations;
let state;
const localVue = createLocalVue();
localVue.use(Vuex);
beforeEach(() => {
actions = {
someAction: jest.fn()
};
mutations = {
someMutation: jest.fn()
};
state = {
key: {}
};
store = new Vuex.Store({
actions,
mutations,
state,
});
wrapper = shallowMount(Login, {
propsData: {},
attachTO: '#root',
mocks: {},
stubs: {},
methods: {},
store,
localVue,
});
});
afterEach(() => {
wrapper.destroy();
});
describe('Component', () => {
test('is a Vue instance', () => {
expect(wrapper.contains('h2')).toBe(true)
});
});
● Test suite failed to run
TypeError: Cannot set property 'content' of null
let apiBaseUrl = document.querySelector('meta[name="apiBaseUrl"]');
7 | if(!apiBaseUrl) {
> 8 | apiBaseUrl.content = '/api/';
| ^
9 | }
10 |
11 | let newAxios = axios.create({
The bug is here:
if(!apiBaseUrl) {
^^^^^^^^^^^
apiBaseUrl.content = '/api/';
}
The !apiBaseUrl condition checks that apiBaseUrl is falsy (null or undefined), and then tries to use it, leading to the error you observed.
Simply remove the ! operator from the condition to fix that error:
if(apiBaseUrl) {
apiBaseUrl.content = '/api/';
}

Access Vue app (this) from non vue file

I'm new to vue (started using vue 2) I'm using Store (vuex) and I'm trying to acheive something.
basically I managed to install the vue-auth plugin : I have this.$auth that I can call from within .vue files.
Now using the store I wanna call the userLogin function by dispatching the call like this from a vue file :
<script>
export default {
computed: {
comparePasswords() {
return this.password === this.passwordConfirm
? true
: "Passwords don't match";
}
},
methods: {
userSignUp() {
if (this.comparePasswords !== true) {
return;
}
this.$store.dispatch("userSignUp", {
email: this.email,
password: this.password
});
}
},
data() {
return {
email: "",
password: "",
passwordConfirm: ""
};
}
};
</script>
in the store/index I'm trying to access the 'this.$auth' I do understand is some kind of context switching but I don't know how to access the vue app instance. :
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
let app = this
export const store = new Vuex.Store({
state: {
appTitle: 'LiveScale Dashboard',
user: null,
error: null,
loading: false
},
mutations: {
setUser(state, payload) {
state.user = payload
},
setError(state, payload) {
state.error = payload
},
setLoading(state, payload) {
state.loading = payload
}
},
actions: {
userLogin({ commit }, payload) {
commit('setLoading', true)
var redirect = this.$auth.redirect(); // THIS IS WRONG.
this.$auth.login({ // THIS IS WRONG.
body: payload, // Vue-resource
data: payload, // Axios
rememberMe: this.data.rememberMe,
redirect: { name: redirect ? redirect.from.name : 'account' },
fetchUser: this.data.fetchUser
})
.then(() => {
commit('setUser', this.context)
commit('setLoading', false)
router.push('/home')
}, (res) => {
console.log('error ' + this.context);
commit('setError', res.data)
commit('setLoading', false)
});
},
userSignUp({ commit }, payload) {
// ...
}
},
getters: {}
})
Thanks for your help
try using Vue.$auth in index.js it should work
The idea (so far) is to pass the instance as an argument to the function as follows :
this.$store.dispatch("userSignUp", {
email: this.email,
password: this.password,
auth: this.$auth //added this line
});
and then in the store -> actions , payload.auth will contain my auth plugin :
userLogin({ commit }, payload) {
commit('setLoading', true)
var redirect = payload.auth.redirect();
payload.auth.login({
body: payload, // Vue-resource
data: payload, // Axios
rememberMe: this.data.rememberMe,
redirect: { name: redirect ? redirect.from.name : 'account' },
fetchUser: this.data.fetchUser
})
.then(() => {
commit('setUser', this.context)
commit('setLoading', false)
router.push('/home')
}, (res) => {
console.log('error ' + this.context);
commit('setError', res.data)
commit('setLoading', false)
});
},
I don't know if it's the best practice or not, but this is how I managed to do it. Please feel free to suggest anything.

Nuxt Auth Module Authenticated User Data

I have an API api/auth that is used to log users in. It expects to receive an access_token (as URL query, from Headers, or from request body), a username, and a password. I've been using the Vue Chrome Developer Tool and even though I get a 201 response from the server, the auth.loggedIn state is still false. I think that might be the reason why my redirect paths on the nuxt.config.js isn't working as well. Can anyone point me to the right direction on why it doesn't work?
This is a screenshot of the Vue Chrome Developer Tool
This is the JSON response of the server after logging in. The token here is different from the access_token as noted above.
{
"token": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"user": {
"user_name": "xxxxxxxxxxxxxxxxxx",
"uid": "xxxxxxxxxxxxxxxxxx",
"user_data": "XXXXXXXXXXXXXXXXXXXXXXXXX"
}
}
Here is the relevant part of nuxt.config.js
export default {
modules: [
'#nuxtjs/axios',
'#nuxtjs/auth',
['bootstrap-vue/nuxt', { css: false }]
],
router: {
middleware: [ 'auth' ]
},
auth: {
strategies: {
local: {
endpoints: {
login: {
url: '/api/auth?access_token=XXXXXXXXXXXXXXXXXXXXXX',
method: 'post',
propertyName: 'token'
},
logout: {
url: '/api/auth/logout',
method: 'post'
},
user: {
url: '/api/users/me',
method: 'get',
propertyName: 'user'
}
}
}
},
redirect: {
login: '/',
logout: '/',
home: '/home'
},
token: {
name: 'token'
},
cookie: {
name: 'token'
},
rewriteRedirects: true
},
axios: {
baseURL: 'http://localhost:9000/'
}
}
And my store/index.js
export const state = () => ({
authUser: null
})
export const mutations = {
SET_USER: function (state, user) {
state.authUser = user
}
}
export const actions = {
nuxtServerInit ({ commit }, { req }) {
if (req.session && req.user) {
commit('SET_USER', req.user)
}
},
async login ({ commit }, { username, password }) {
const auth = {
username: username,
password: password
}
try {
const { user } = this.$auth.loginWith('local', { auth })
commit('SET_USER', user)
} catch (err) {
console.error(err)
}
}
}
The login action in the store is triggered by this method in the page:
export default {
auth: false,
methods: {
async login () {
try {
await this.$store.dispatch('login', {
username: this.form.email,
password: this.form.password
})
} catch (err) {
this.alert.status = true
this.alert.type = 'danger'
this.alert.response = err
}
}
}
}
P.S. I realize I'm explicitly including the access_token in the URL. Currently, I don't know where a master_key or the like can be set in the Nuxt Auth Module.
Try this in your store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = () => new Vuex.Store({
state: {
authUser: null
},
mutations: {
SET_USER: function (state, user) {
state.authUser = user
}
},
actions: {
CHECK_AUTH: function(token, router) {
if (token === null) {
router.push('/login')
}
}
}
})
export default store
And for the router, this should work globally:
$nuxt._router.push('/')

`moxios.wait` never executes when testing VueJS with Jest, Vue Test Utils and Moxios

I'm trying to write my first unit test for a VueJS component that grabs some data from an API endpoint when it renders:
My VueJS component:
import axios from 'axios';
export default {
props: {
userIndexUrl: {
required: true
}
},
data() {
userList: [],
tableIsLoading: true
},
created() {
const url = this.userIndexUrl;
axios.get(url)
.then(response => {
this.userList = response.data.data;
this.tableIsLoading = false;
})
},
}
and my test:
import { mount } from 'vue-test-utils'
import moxios from 'moxios'
import UsersAddRemove from 'users_add_remove.vue'
describe('UsersAddRemove', () => {
const PROPS_DATA = {
userIndexUrl: '/users.json'
}
beforeEach(() => {
moxios.install();
moxios.stubRequest('/users1.json', {
status: 200,
response: {
"count": 1,
"data": [
{ "id" : 1,
"email" : "brayan#schaeferkshlerin.net",
"first_name" : "Kenneth",
"last_name" : "Connelly"
}
]
}
});
});
afterEach(() => {
moxios.uninstall();
});
it('loads users from remote JSON endpoint and renders table', () => {
const wrapper = mount(UsersAddRemove, { propsData: PROPS_DATA });
moxios.wait(() => {
expect(wrapper.html()).toContain('<td class="">brayan#schaeferkshlerin1.net</td>');
done();
});
});
});
So what I expect to happen is the component gets instantiated, after which it does an API call, which is caught by Moxios, after which it should execute moxios.wait, which isn't happening. The tests seem to ignore moxios.wait, and it passes successfully even when it shouldn't.
What am I missing here?
Try adding the done as argument:
it('loads users from remote JSON endpoint and renders table', (done) => {
// -----------------------------------------------------------^^^^^^
const wrapper = mount(UsersAddRemove, { propsData: PROPS_DATA });
moxios.wait(() => {
expect(wrapper.html()).toContain('<td class="">brayan#schaeferkshlerin1.net</td>');
done();
});
});
Waiting for the Ajax
Change as follows:
// remove the stubRequest of beforeEach()
beforeEach(() => {
moxios.install();
});
// afterEach stays the same
it('loads users from remote JSON endpoint and renders table', (done) => {
const wrapper = mount(UsersAddRemove, { propsData: PROPS_DATA });
moxios.wait(() => {
let request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: {
"count": 1,
"data": [{
"id": 1,
"email": "brayan#schaeferkshlerin.net",
"first_name": "Kenneth",
"last_name": "Connelly"
}]
}
}).then(function() {
expect(wrapper.html()).toContain('<td class="">brayan#schaeferkshlerin1.net</td>');
done();
});
});
});