i'm trying to write a test for one of my filters in my Vue project using jest , can i Test that filter without using it in any component, i mean can i test it as a unit(like a function)? i searched a lot but i couldn't find anything to show me how to write an individual test for a filter in Vue
import Vue from 'vue'
export default function () {
Vue.filter('englishNumber', (value) => {
if (value === '۰') return 0
if (!value) return ''
if (typeof value !== 'string') {
value = value.toString()
}
return value.replace(/[\u06F0-\u06F9]+/g, function (digit) {
let ret = ''
for (let i = 0, len = digit.length; i < len; i++) {
ret += String.fromCharCode(digit.charCodeAt(i) - 1728)
}
return ret
})
})
}
this is the filter i want to test
does anyone know how to write this kind of test ?
If you're writing filters used in multiple components, then it's quite easy to test.
Since Vue.filter simply takes a function, you can just write a test for the function independently of the filter by exporting the definition, like so:
// Define the function independently and export it
export const englishNumber = function (value) {
if (value === '۰') return 0
if (!value) return ''
if (typeof value !== 'string') {
value = value.toString()
}
return value.replace(/[\u06F0-\u06F9]+/g, function (digit) {
let ret = ''
for (let i = 0, len = digit.length; i < len; i++) {
ret += String.fromCharCode(digit.charCodeAt(i) - 1728)
}
return ret
})
}
// Pass the function in to the filter defintion
Vue.filter('englishNumber', englishNumber)
Then in your test file, import the function and test it like you do anything else:
import { englishNumber } from '#/lib/filters.js'
describe("englishNumber") {
it("does whatever") {
expect(englishNumber("actual")).toEqual("expected")
}
}
We can test filters with hand-made components. For example we have a filter that transform date into needed format:
// filters.js
import Vue from "vue";
Vue.filter("dateFormat", (incomingDate) => {
// does the work here
}
So we can test it the following way:
// filters.spec.js
import Vue from "vue";
import "#/components/filters";
import { mount, createLocalVue } from "#vue/test-utils";
const myComponent = Vue.component("myComponent", {
data() {
return {
date: new Date("January 1, 2020 01:10:30")
};
},
template: "<p> {{ date | dateFormat }} </p>"
});
const localVue = createLocalVue();
describe("filter dateFormat", () => {
it("filter transforms date to readable format", () => {
const wrapper = mount(myComponent, {
localVue
});
expect(wrapper.html()).toBe("<p> 01 January 2020 </p>");
});
});
The correct way to unit test a Vue filter would be to declare it locally inside your component (using Vue's API and Vue.filter).
You can then write unit tests inside your Vue component.
You can check this case
Related
I need to get the cookies value on the first render. I get those in _app.tsx.
Everything looks fine (render correctly the html) but I get the server/client mismatch warning because at the first render on Server, cookies are undefined and the value fall back to default value which is 0.
On hydration, the value is picked from cookies and is displayed correctly.
Could someone explain to me why is a problem that on the server the value is the default value (therefor why I get this warning) and what would be a better way to write this code?
Here my _app.tsx
import React, { useState, useEffect } from 'react'
import type { AppProps } from 'next/app'
import { UserContext } from '../context/UserContext'
require('es6-promise').polyfill()
let cookieHelper
if (typeof window !== 'undefined') {
cookieHelper = require( '../helpers/_cookies' ) // This is a file written by us where we export const and get/set cookies func
}
function ElliotApp ({ Component, pageProps }: AppProps) {
useEffect(() => {
import('../helpers/_cookies')
}, [])
const searchesFromCookies = cookieHelper?.get(cookieHelper?.COOKIE_NAME_SEARCH_COUNT) // this value is a string like '3'
const userState = {
numOfSearches: searchesFromCookies || 0
}
const [userContext, setUserContext] = useState(userState)
useEffect(() => {
cookieHelper?.set(cookieHelper?.COOKIE_NAME_SEARCH_COUNT, userContext.numOfSearches)
}, [userContext])
return (
<UserContext.Provider value={[userContext, setUserContext]}>
<Component {...pageProps} />
</UserContext.Provider>
)
}
export default ElliotApp
many thanks!
Could someone explain to me why is a problem that on the server the value is the default value
Probably because your cookieHelper is just reading cookies from document.cookie and there is no such thing on the server.
If you want to get cookie with SSR you could use getInitialProps:
function parseCookies(req) {
// cookie.parse is some function that accepts cookie string and return parsed object
return cookie.parse(req ? req.headers.cookie || "" : document.cookie)
}
ElliotApp.getInitialProps = async ({ req }) => {
const cookies = parseCookies(req)
return {
searchesFromCookies: cookies[COOKIE_NAME_SEARCH_COUNT]
}
}
and then do something with them in your App component:
function ElliotApp ({ Component, pageProps, searchesFromCookies }: AppProps) {
const userState = {
numOfSearches: searchesFromCookies || 0
}
const [userContext, setUserContext] = useState(userState)
// do whatever ...
}
EDIT:
In case you are fine with default value on the server then you just need to do everything inside useEffect hook (it wont run on the server):
function ElliotApp ({ Component, pageProps }: AppProps) {
const userState = {
numOfSearches: 0
}
const [userContext, setUserContext] = useState(userState)
useEffect(() => {
setUserContext({
numOfSearches: cookieHelper.get(cookieHelper.COOKIE_NAME_SEARCH_COUNT)
});
}, []);
useEffect(() => {
cookieHelper.set(cookieHelper.COOKIE_NAME_SEARCH_COUNT, userContext.numOfSearches)
}, [userContext])
return (
<UserContext.Provider value={[userContext, setUserContext]}>
<Component {...pageProps} />
</UserContext.Provider>
)
}
I am writing unit tests for a pagination module, and it has a simple VueX store module.
I am using Vue.js 2.5 and Mocha/Chai/Sinon for tests. Setup is with Vue CLI 3.
The problem is that when the currentPage is incremented in the store in one unit test, this state persists into the next test even when I try to create a fresh store.
I have attempted to return a fresh pagination module by using a function that returns an Object.assign() fresh copy but this did not work. I have left this in the code as shown in the spec below.
store/pagination.js
const state = {
currentPage: 0
}
export const getters = {
currentPage: state => {
return state.currentPage
}
}
export const actions = {
nextPage ({ commit, state }) {
commit('setCurrentPage', state.currentPage + 1)
}
}
export const mutations = {
setCurrentPage (state, page) {
state.currentPage = page
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
Pagination.spec.js
function getPaginationStore () {
return Object.assign({}, pagination)
}
describe('Paginate.vue', () => {
let localVue
let wrapper
let store
beforeEach(() => {
localVue = createLocalVue()
localVue.use(Vuex)
store = new Vuex.Store({
modules: {
pagination: getPaginationStore()
}
})
wrapper = shallowMount(Pagination, {
localVue,
propsData: {
items: [],
size: 24
},
store
})
})
afterEach(() => {
store = null
})
it('state should be 0', () => {
expect(wrapper.vm.pageNumber).to.equal(0)
wrapper.vm.$store.dispatch('pagination/nextPage')
expect(wrapper.vm.pageNumber).to.equal(1)
})
it('state should be 0 again but is 1', () => {
// THIS TEST FAILS. IT IS ACTUALLY 1
expect(wrapper.vm.pageNumber).to.equal(0)
})
})
The solution was to use a function for the state in the module rather than a plain old javascript object. Here is my new store state code:
export const state = () => {
return {
currentPage: 0
}
}
Answer was provided by #SumNeuron from the Vue discord channel.
After 3 days researching and not ariving anywhere, I decided to ask here for someone that already have similar experience or can point a better path to follow.
The better SO question I've found was this but left some questions in air: React - how to test form submit?
Since I'm begginer I believe I may getting something wrong, but no sure exactly which. If it's the way I build the components or even test concept itself.
I have the following case:
When a user logins in, it calls API (mock) then save token result (when successful) to localStorage (mock)
When user is already logged in, it gets redirected to homepage
My code until now:
Login Component
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: ''
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleSubmit(e) {
e.preventDefault();
this.props.sendLoginRequest(this.state).then(
({data}) => {
console.log(data);
},
(data) => {
console.error(data);
}
);
}
handleChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
render() {
return (
<div id='auth-container' className='login'>
<Form onSubmit={this.handleSubmit}>
<FormGroup controlId='emailaddress'>
<InputGroup bsSize='large'>
<InputGroup.Addon>
<Icon glyph='icon-fontello-mail' />
</InputGroup.Addon>
<FormControl
autoFocus
className='border-focus-blue'
type='email'
placeholder='email#fixdin.com'
name='email'
onChange={this.handleChange}
value={this.state.email} />
</InputGroup>
</FormGroup>
<FormGroup controlId='password'>
<InputGroup bsSize='large'>
<InputGroup.Addon>
<Icon glyph='icon-fontello-key' />
</InputGroup.Addon>
<FormControl
className='border-focus-blue'
type='password'
placeholder='password'
name='password'
onChange={this.handleChange}
value={this.state.password} />
</InputGroup>
</FormGroup>
</Form>
</div>
)
}
}
Login.propTypes = {
sendLoginRequest: React.PropTypes.func.isRequired
}
authAction.js
import createApi from '../services/api';
import { saveToken } from '../services/session';
export function sendLoginRequest(loginData) {
return dispatch => {
const api = createApi();
const loginPromise = api.post('auth/', loginData);
loginPromise.then(
({ data }) => {
saveToken(data.token);
}
);
return loginPromise;
}
}
API..js
import axios from 'axios';
import { isAuthenticated, getToken } from './session';
export const BASE_URL = 'http://localhost:8000/api/v1/';
export default function createAPI() {
let auth = { }
if (isAuthenticated()) {
auth = {
Token: getToken()
}
}
return axios.create({
baseURL: BASE_URL,
auth: auth
});
};
session.js
const TOKEN_KEY = 'token';
export function saveToken(value)
{
localStorage.setItem(TOKEN_KEY, value);
}
export function getToken()
{
return localStorage.getItem(TOKEN_KEY)
}
export function isAuthenticated() {
return getToken() !== null;
}
My test stack is Mocha/Chai/Enzyme/sinon and it's defined
setup.js
var jsdom = require('jsdom');
class LocalStorageMock {
constructor() {
this.store = {};
}
clear() {
this.store = {};
}
getItem(key) {
return this.store[key];
}
setItem(key, value) {
this.store[key] = value.toString();
}
};
if(!global.document) {
global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = document.defaultView;
global.navigator = {userAgent: 'node.js'};
global.localStorage = new LocalStorageMock;
}
login-test.js
import React from 'react';
import sinon from 'sinon';
import { mount, shallow } from 'enzyme';
import { expect } from 'chai';
import { Provider } from 'react-redux';
import axios from 'axios'
import moxios from 'moxios'
import store from './../src/store';
import LoginPage from './../src/auth/components/Login';
describe('Login', () => {
beforeEach(function () {
moxios.install(axios)
})
afterEach(function () {
moxios.uninstall(axios)
})
it('should call action on form submit', () => {
const submitRequest = sinon.stub(LoginPage.prototype, 'handleSubmit').returns(true);
const wrapper = mount(<Provider store={store}><LoginPage /></Provider>);
wrapper.find('form').simulate('submit');
expect(submitRequest.called).to.be.true;
submitRequest.restore();
});
it('should save token on succesfull login', () => {
const wrapper = mount(<Provider store={store}><LoginPage /></Provider>);
const emailInput = wrapper.find('input[type="email"]');
const passInput = wrapper.find('input[type="password"]');
const form = wrapper.find('form');
emailInput.value = "valid#email.com";
passInput.value = '123456789';
form.simulate('submit'); // Should I use submit button instead???
moxios.wait(function () {
let request = moxios.requests.mostRecent()
request.respondWith({
status: 200,
response:
{ Token: 'validToken' }
}).then(function () {
expect(localStorage.getItem('Token')).to.equal('validToken');
});
});
});
});
Above test does not pass, since it returns false for submitRequest.called and second test fails with error "Cannot read property 'respondWith' of undefined". I'm not sure how to fix and more, I'm not sure if I idealized it right!!
When doing a lot of research about it, I've seen examples with tests specific for component method call + isolated action test.
So...
When I think about "click login and save token" I'm overthinking a unit test? There's a better way to test things like that? Maybe separate some concerns?
This is the correctly way to test if a form submit invoke its callback? If so, why sinon is not working there?
This is the correctly way to mock + test api call to login and localStorage? If so, why Moxios is not working properly? It keeps giving me that mostRecent() is undefined.
If no, to question 2 and 3, where can I find a valid and working example of how to properly test cited behavior?
Thanks in advance.
I have below react code and i am using webpack for unit test.
export function fetchFiltersIfNeeded(filterData) {
let sendDate = (new Date()).getTime();
return (dispatch, getState) => {
if (shouldFetchFilters(getState(), filterData)) {
return dispatch(fetchFilters(filterData, sendDate));
}
};
}
How do I test the function fetchFiltersIfNeeded ? How do I mock it ?
Use redux-mock-store to mock the store
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
const mockStore = configureMockStore([thunk])
describe('Actions', () => {
it('fetchFiltersIfNeeded', () => {
const store = mockStore({ /* you can put initial state here */ })
store.dispatch(fetchFiltersIfNeeded(yourTestData))
// store.getActions() will return an array of dispatched actions
expect(store.getActions()[0]).to.deep.equal({ type: 'FETCH_FILTERS' })
})
})
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