How to test methods in functional component for nextjs components - unit-testing

Component:
const Demo = () => {
const handler = () => {
// some logic written
};
return (
<div>
<button data-testid={'handler_id'} onClick={() => handler()}>
Run Handler method
</button>
</div>
);
};
export default Demo;
test:
test('test handler method', () => {
render(<Demo />)
act(() => {
fireEvent.click(screen.getByTestId('handler_id'));
});
// some expectations below
})
I am unable to trigger handler method. Even in codeCoverage i dont see this method is covered.
Please help on how to write test cases for methods inside a component. All examples show that the method is passed to component from the props. But here my method is in the component itself.

Related

Vue.js unit testing ErrorBoundary

I've built simple ErrorBoundary component for my project in Vue.js and I'm struggling to write unit test for it. Component's code below:
<template>
<div class="overvue-error-boundary">
<slot v-if="!error" />
<div class="error-message" v-else>Something went horribly wrong here.</div>
</div>
</template>
<script>
export default {
data () {
return {
error: false
}
},
errorCaptured (error, vm, info) {
this.error = true;
}
}
</script>
I've created an ErrorThrowingComponent that throws an error on created() lifecycle hook so I can test ErrorBoundary:
const ErrorThrowingComponent = Vue.component('error-throwing-component', {
created() {
throw new Error(`Generic error`);
},
render (h) {
return h('div', 'lorem ipsum')
}
});
describe('when component in slot throws an error', () => {
it('renders div.error-message', () => {
// this is when error is when 'Generic error' is thrown by ErrorThrowingComponent
const wrapper = shallowMount(OvervueErrorBoundary, {
slots: {
default: ErrorThrowingComponent
}});
// below code is not executed
expect(wrapper.contains(ErrorThrowingComponent)).to.be.false;
expect(wrapper.contains('div.error-message')).to.be.true;
});
});
The problem is that ErrorThrowingComponent throws an error when I'm trying to actually mount it (thus failing entire test). Is there any way I can prevent this from happening?
EDIT: What I'm trying to achieve is to actually mount the ErrorThrowing component in a default slot of ErrorBoundary component to assert if ErrorBoundary will render error message and not the slot. This is way I created the ErrorThrowingComponent in the first place. But I cannot assert ErrorBoundary's behavior, because I get an error when trying to create a wraper.
For anyone comming here with a similar problem: I've raised this on Vue Land's #vue-testing channel on Discord, and they suggested to move entire error-handling logic to a function which will be called from the errorCaptured() hook, and then just test this function. This approach seems sensible to me, so I decided to post it here.
Refactored ErrorBoundary component:
<template>
<div class="error-boundary">
<slot v-if="!error" />
<div class="error-message" v-else>Something went horribly wrong here. Error: {{ error.message }}</div>
</div>
</template>
<script>
export default {
data () {
return {
error: null
}
},
methods: {
interceptError(error) {
this.error = error;
}
},
errorCaptured (error, vm, info) {
this.interceptError(error);
}
}
</script>
Unit test using vue-test-utils:
describe('when interceptError method is called', () => {
it('renders div.error-message', () => {
const wrapper = shallowMount(OvervueErrorBoundary);
wrapper.vm.interceptError(new Error('Generic error'));
expect(wrapper.contains('div.error-message')).to.be.true;
});
});

Vue.js test:unit with test-utils and Jest : Nested component -Is it possible to mock the $emit(function) from child component?

Given nested components
the Heading.vue component
{{ $t("lang.views.home.heading.btn__listen") }}
play_arrow
The nested child component AuioPlayer.vue
<template>
<div style="display: inline-block;">
<v-btn id="playPauseBtn">
...
</v-btn>
<v-btn id="stopBtn" outline icon class="inline teal--text" #click.native="stop()">
<v-icon>stop</v-icon>
</v-btn>
<v-btn id="muteBtn">
...
</v-btn>>
</div>
</template>
<script>
...
methods: {
stop() {
this.$data._howl.stop();
this.$emit("playerStop");
},
...
</script>
Is it possible to test the parent Heading.vue , using shallowMount() mocking the $emit("playerStop") event ... ?
it("should display LISTEN button on child component audioplayer event stop()", () => {
// given
wrapper = shallowMount(Heading, { router, i18n });
wrapper.vm.listening = true;
// when
// audioplayer child component should be stubbed
const audioplayer = wrapper.find('#audioplayer');
console.log(audioplayer.html());
// mock the $emit(playerStop) from the child audioplayer stub
expect(wrapper.vm.listening).toBe(false);
});
UPDATE
I trued 2 solutions without any success
1 / using a spy function
it("should display LISTEN button on child component audioplayer event stop()", () => {
// given
wrapper = shallowMount(Heading, { router, i18n });
const spy = jest.fn();
// wrapper.vm.$on('stopPlayer', spy); // focus on the call of listener
wrapper.setData({ listening: true });
const audioplayer = wrapper.find('audioplayer-stub');
// when
audioplayer.trigger('stopPlayer');
// then
expect(spy).toHaveBeenCalledTimes(1);
expect(wrapper.vm.listening).toBe(false);
});
2 / Using an async $emit()
it("should display LISTEN button on child component audioplayer event stop()", async () => {
// given
wrapper = shallowMount(Heading, { router, i18n });
wrapper.setData({ listening: true });
const audioplayer = wrapper.find('audioplayer-stub');
// when
audioplayer.vm.$emit('stopPlayer');
await wrapper.vm.$nextTick();
// then
expect(wrapper.vm.listening).toBe(false);
});
In both cases it seems that if I trigger or emit from the sub-component nothing happen...
As a matter of fact, the emit() should be done from a stop button in the sub-component which is not stubbed at this level ..
Is there anyway to stub it ?
I want to avoid a mount ... using shallowMount should be sufficient at this level of tets ...
thanks for feedback
SOLVED ... this is one of the traps to avoid while unit testing vue.js : What should I test ?, not testing the wrong thing....
using test-utils w shallowMount, I should not test for the emi() event from a stubbed component ( this should be tested later within this component) I should only test the method which will be called ...
In tis case
methods: {
playerStop() {
this.listening = false;
}
}
tested simply with
it("method playerStop should tpggle listening to false", async () => {
// given
wrapper = shallowMount(Heading, { router, i18n });
wrapper.setData({ listening: true });
// when
wrapper.vm.playerStop();
const playBtn = wrapper.find('#playBtn')
// then
expect(wrapper.vm.listening).toBe(false);
});

Unit Testing VueJS - Checking for an element with a certain class name

I'm struggling to get a simple unit test working in a VueJS app.
I'm basically trying to test the template and check if it contains a div element with a class name of "version" but the test keeps failing with an undefined is not a constructor (evaluating 'expect(component.$el.querySelector('div')).to.have.className('version')'). error.
It's a simple component with this as the template:
<template>
<div>
<div class="version">
<label>Version {{ version }}</label>
</div>
<div class="activityBanner">
<label>{{ user }}</label>
<router-link id="logout" :to="{name: 'logout' }">
<label>Logout</label>
</router-link>
</div>
</div>
</template>
Here is the unit test I'm working with:
import Vue from 'vue';
import router from '#/router';
import VueRouter from 'vue-router';
import Navigation from '#/components/Navigation';
describe('Navigation.vue', () => {
// Nice little helper to return our component within a div
const getComponent = data => {
const Constructor = Vue.extend(Navigation);
return new Constructor({
router
}).$mount();
};
describe('Component', () => {
it('should have a property "name"', () => expect(Navigation.name).to.be.a('string'));
it('should have a property "name" set to "Navigation"', () => expect(Navigation.name).to.equal('Navigation'));
it('should have a data hook', () => expect(Navigation.data).to.be.a('function'));
it('should have a default "currentView" set to "profileTable"', () => {
const defaultData = Navigation.data();
expect(defaultData.currentView).to.equal('profileTable');
});
it('should have a default "version" set to "0.5"', () => {
const defaultData = Navigation.data();
expect(defaultData.version).to.equal(0.5);
});
it('should have a default "user" set to "Bob Barker"', () => {
const defaultData = Navigation.data();
expect(defaultData.user).to.equal('Bob Barker');
});
});
describe('Template', () => {
Vue.use(VueRouter);
it('should render correctly', () => {
const component = getComponent();
expect(component.$el);
});
it('should have a "div" element', () => {
const component = getComponent();
expect(component.$el.querySelectorAll('div').length);
});
it('should have a element with a "className" set to "version"', () => {
const component = getComponent();
expect(component.$el.querySelector('div')).to.have.className('version');
});
});
});
What are I doing wrong?
Checkout the Vue test utils:
Vue Test Utils
It makes very light work of testing templates.
Here is there example:
import { mount } from '#vue/test-utils'
import Counter from './counter'
describe('Counter', () => {
// Now mount the component and you have the wrapper
const wrapper = mount(Counter)
it('renders the correct markup', () => {
expect(wrapper.html()).toContain('<span class="count">0</span>')
})
// it's also easy to check for the existence of elements
it('has a button', () => {
expect(wrapper.contains('button')).toBe(true)
})
})

How to unit test a Redux action in a component inside a connected Redux component with Jest

I'm using jest and enzyme to unit test my React application and I'm struggling with testing connected components.
I do have a simple component which the following logic:
class LoginPage extends React.Component {
componentDidMount() {
if (!this.props.reduxReducer.appBootstrapped) {
this.props.dispatch(ReduxActions.fadeOutAndRemoveSplashScreen(500));
}
}
render() {
return (
<div data-page="login-page" >
<div>This is the login page.</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
reduxReducer: state.reduxReducer
}
};
export default connect(mapStateToProps, null)(LoginPage);
So, this is a component which displays a <div /> element containing some text, but the important part that I want to test is that when the component is mounted, an action is dispatched to hide the splash screen.
I want this only to happen when the application is not bootstrapped.
I do have a simple unit test to test that the component is rendered:
describe("[LoginPage Component]", () => {
it("Renders without a problem.", () => {
// Act.
const wrapper = mount(
<LoginPage store={ reduxStore } />
);
// Assert.
expect(wrapper.find("div[data-page=\"login-page\"]").length).toBe(1);
});
});
The reduxStore property is my actual redux store, created with the following code:
const reduxStore = createStore(
combineReducers(
{
reduxReducer
}
)
);
Now, how can I test the componentDidMount() method, and more in special, test that the redux action fadeOutAndRemoveSplashScreen() is only called when the application is not bootstrapped yet.
I do think that I need to mock my redux store, however, I'm a newbie on this and don't now how to get started, so an example will be highly appreciated.
If any other thoughts on my implementation, feel free to provide some advice.
Kind regards
I wouldn't use the raw dispatch method to send off an action. I would use mapDispatchToProps. This makes your action directly available in your component props - here we use ES6 destructing as a short hand in the connect method.
Then instead of mocking the redux store I would just test your component without it. Try adding an export to your class (first line). For example:
export class LoginPage extends React.Component {
componentDidMount() {
if (!this.props.reduxReducer.appBootstrapped) {
// make sure that you are using this.props.action() not
// just the action(), which is not connected to redux
this.props.fadeOutAndRemoveSplashScreen(500);
}
}
render() {
return (
<div data-page="login-page" >
<div>This is the login page.</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
reduxReducer: state.reduxReducer
}
};
export default connect(mapStateToProps, {
fadeOutAndRemoveSplashScreen: ReduxActions.fadeOutAndRemoveSplashScreen
})(LoginPage);
Then in your test instead of importing the connected component, import the class:
import ConnectedLoginPage, { LoginPage } from '/path/to/component';
Then simply pass the LoginPage whatever props you want to test with. So we will set your appBooststrapped to false, and then pass the action as a sinon spy:
const spy = sinon.spy();
const reduxReducer = {
appBootstrapped: false, // or true
}
const wrapper = mount(
<LoginPage reduxReducer={reduxReducer} fadeOutAndRemoveSplashScreen={spy} />
);
// test that the spy was called
expect(spy.callCount).to.equal(1);
This makes the test much simpler, and more importantly you are testing the component behavior - not Redux.

How to mock e.preventDefault in react component's child

Hy, I don't know how to mock an inline function in React component's child
My stack: sinon, chai, enzyme;
Component usage:
<ListItem onClick={() => someFn()} />
Component's render:
render() {
return (
<li>
<a href="#" onClick={e => {
e.preventDefault();
this.props.onClick();
}}
> whatever </a>
</li>
);
}
Here we have onClick function that calls e.preventDefault(). How to tell to <a href>(link) to not to call e.preventDefault()? How can I mock an onClick?
Below is what I have tried in tests:
Shallow copy setup
function setup() {
const someFn = sinon.stub();
const component = shallow(
<ListItem
onClick={() => {
someFn();
}}
/>
);
return {
component: component,
actions: someFn,
link: component.find('a'),
listItem: component.find('li'),
}
}
And the test
it('simulates click events', () => {
const { link, actions } = setup();
link.simulate('click'); //Click on <a href>
expect(actions).to.have.property('callCount', 1); //will be fine if we remove e.preventDefault()
});
Test's output error:
TypeError: Cannot read property 'preventDefault' of undefined
Try this
link.simulate('click', {
preventDefault: () => {
}
});
test('simulates click events', () => {
const e = { stopPropagation: jest.fn() };
const component = shallow(<ListItem{...props} />);
const li = component.find('li').at(0).childAt(0)
li.props().onClick(e)
expect();
});
For those using Jest and #testing-library or react-testing-librarys fireEvent, you need to provide an initialised event object, otherwise the event can't be dispatched via your element.
One can then assert on e.preventDefault being called by assigning a property to that initialised event:
test('prevents default on click', () => {
const {getByText} = render(<MyComponent />);
const button = getByText(/click me/);
// initialise an event, and assign your own preventDefault
const clickEvent = new MouseEvent('click');
Object.assign(clickEvent, {preventDefault: jest.fn()});
fireEvent(button, clickEvent);
expect(clickEvent.preventDefault).toHaveBeenCalledTimes(1);
});
Similarly for stopPropagation.
Anton Karpenko's answer for Jest was useful.
Just to note that this is an issue only when using shallow enzyme renderer. In case of full DOM renderer mount, the event object contains the preventDefault method, therefore you don't have to mock it.
You can define an object with regarding function you will mock via some testing tool, for example look at Jest and Enzyme
describe('Form component', () => {
test('deos not reload page after submition', () => {
const wrapper = shallow(<TodosForm />)
// an object with some function
const event = { preventDefault: () => {} }
// mocks for this function
jest.spyOn(event, 'preventDefault')
wrapper.find('form').simulate('submit', event)
// how would you know that function is called
expect(event.preventDefault).toBeCalled()
})
})
I would suggest to create new object based on jest.fn() with
const event = Object.assign(jest.fn(), {preventDefault: () => {}})
then use it:
element.simulate('click', event);
I am using Web Components and this works for me -
const callback = jest.fn();
MouseEvent.prototype.stopPropagation = callback;
const element = createElement({});
element.shadowRoot.querySelector('ul').click();
expect(callback).toHaveBeenCalledTimes(1);