So after I read common tips and watched the video about testing provided by Vue.js I decided to test only behaviors of my components. But I maybe misunderstand the concept. Here is the component:
<template>
<es-btn
class="a"
round
outline
color="primary"
#click="skipScan"
>
Skip Upload
</es-btn>
</template>
<script>
export default {
data: () => ({
uploadScanningDialog: false,
}),
methods: {
// public functions here which will be tested
skipScan() {
hideDialog();
triggerSkipScan();
},
},
}
// private functions here as vue.js suggests
function hideDialog() {
this.uploadScanningDialog = false;
}
....
</script>
I want to test the behavior:
it('should hide itself and show next dialog when Scan Button is clicked', () => {
const data = {
uploadScanningDialog: true,
};
wrapper.setData(data);
expect(wrapper.vm.uploadScanningDialog).toBeTruthy();
wrapper.find('.a').trigger('click');
expect(wrapper.vm.uploadScanningDialog).toBeFalsy();
});
So here are the questions and errors:
Should I do test this way, by triggering the action instead of calling the method itself? Because I used to test by calling the method and expecting some results, not triggering the component who calls the method
Shouldn't I test the private functions? Because my test tries to call real method, so the test I share gets error. I will mock them but I'm not sure how to continue
Generally it is better to unit test your code through its public api, where possible.
This improves maintainability, as:
It allows you to refactor any private functions, without having to update your unit tests.
If you make changes to your private functions, you can then assert that you haven't broken anything by running your unit tests against your public api.
Whether you run your unit tests by calling a public method or triggering a DOM event is a matter of choice.
The latter gives greater test coverage as you are also testing if you have wired up the event to the event handler function correctly in your template; but it is slightly less maintainable, as if you change your template you may have to update your tests.
Related
I'm currently trying to get complete test coverage on my react app however I'm stuck with jest when trying to test the callback event params from material UI components.
I thought by testing the escape event I could cover the onClose parameter but it is still showing as untested.
Example of that test:
function renderWithRedux(
ui: any,
startingState: any = initialState,
store?: any
) {
if (!store) {
store = createStore(reducer, startingState);
}
return {
...render(<Provider store={store}>{ui}</Provider>),
// adding `store` to the returned utilities to allow us
// to reference it in our tests (just try to avoid using
// this to test implementation details).
store,
};
}
test("Should close the dialog on exit event eg esc key pressed", () => {
const { container, queryByTestId } = renderWithRedux(
<PermissionGroupList />,
permissionGroupCat
);
fireEvent(
queryByTestId("add-group"),
new MouseEvent("click", {
bubbles: true,
cancelable: true,
})
);
let dialogBox = queryByTestId("add-group-dialog");
// Check that the dialog is open.
expect(dialogBox).toBeTruthy();
// Check that the dialog it closes.
fireEvent.keyDown(document.body, {
key: "Escape",
keyCode: 27,
which: 27
})
setTimeout(() => {
// Try to re get the element.
dialogBox = queryByTestId("add-group-dialog");
expect(dialogBox).toBeNull();
}, 500);
})
Same or similar issue when passing the bound closeDialog method to the child component. It appears as untested. How would I test this / will it be covered with the tests of the children component if it fires the method (on the child), I have not yet created the child component tests.
As you can see in the screenshot above both these lines come back as untested, so how do I cover these with my tests.
I'm using react-testing-library and jest --coverage with redux and react-redux.
Your running async code in a sync test. If you use setTimeout in your test then you need to pass in a done() function and then call it when your last async event has finished.
https://jestjs.io/docs/en/asynchronous
I recommend not creating arrow functions as props. Here's a more detailed explanation of the the drawbacks and alternate approaches:
https://medium.com/#oleg008/arrow-functions-in-react-f782d11460b4
This would also make it so those props would be covered by your tests. Though you may also need to test that the class method handleGroupDialog is called. You can do this with a spy:
https://remarkablemark.org/blog/2018/06/13/spyon-react-class-method/
David is also right! You'll want to use a done method or convert your test to async and await.
intro:
I want to test that if I click on the submit button, the onSubmit function is called. I assume this is possible from what I understand when I read the documentation:
https://sinonjs.org/releases/v6.1.5/spies/
https://vue-test-utils.vuejs.org/guides/#testing-key-mouse-and-other-dom-events
expected output:
get the test to run and show me either pass or fail
actual output:
none, I'm currently stuck at the following:
context:
in my test:
import NavBar from '#/components/layout/NavBar.vue'
in that component I have a (simplified version here) form:
<b-nav-form #submit="onSubmit">
<b-form-input />
<b-button type="submit">Search</b-button>
</b-nav-form>
I want to test that if I click on the submit button, the onSubmit function is called.
My setup is Vue, BootstrapVue and Sinon. I understand I have to setup a spy that listens to a function being called.
This is the actual script in my component if that is helpful:
<script>
export default {
data () {
return {
query: ''
}
},
methods: {
onSubmit () {...}
}
}
</script>
example that I understand:
it('a true example', () => {
var f = {
onSubmit: function(query) {
this.query = query;
}
}
var onSubmitSpy = sinon.spy(f, 'onSubmit');
f.onSubmit('Club')
console.log(onSubmitSpy.callCount); // is 1
onSubmitSpy.restore();
})
But this is not connected to for example clicking on the button in the form.
Please advise
The idea to test functions of vue components to have been called is to:
Create testing components with vue-test-utils mount or shallowMount.
Pass a methods param in the options to provide spies.
Perform actions in the component that calls the spied method, then assert the method was really called.
I don't have sinon experience, am only used to test vue components with jest, but the thing should be something like the following:
import NavBar from '#/components/layout/NavBar.vue'
import {shallowMount} from 'vue-test-utils';
it('asserting onSubmit calls', () => {
// given
var onSubmit = sinon.spy();
var wrapper = shallowMount(NavBar, {
methods: {
onSubmit();
}
});
var vm = wrapper.vm;
// when
vm.onSubmit();
// then (I dont really dont know if this is valid sinon syntax)
assertTrue(onSubmit.called);
// or, with jest syntax:
// expect(onSubmit).toHaveBeenCalled();
})
Now, the code snippet should work, but there are problems with this test: we are asserting that when we call the component onSubmit, the onSubmit spy gets called. This is not the great thing.
Your test would probably need to assert somehing like: when the <b-nav-form> component emits a submit event, then the onSubmit spy gets called.
That would be a more complex test, for two reasons:
Because a child component is involved. To really render child components in a vue-test-utils test, you need to use mount instead of shallowMount. This is difficult as you need to provided childs props and dependencies, so get used to the shallowMount and mount differences.
When you start testing events, chances are some synchrony is involved, so you need to wait for the event to propagate and get your component method called. This usually involves to pass done callback to it() blocks.
just starting to dive into testing world and one thing confuses me. I have class like this:
class TestClass {
static staticMethod () {
methodOne();
methodTwo();
}
and test like this:
test('should call methodOne function', () => {
TestClass.staticMethod();
expect(methodOne).toHaveBeenCalled();
});
test('should call methodTwo function', () => {
Test.staticMethod();
expect(methodTwo).toHaveBeenCalled();
expect(methodTwo).toHaveBeenCalledTimes(1);
});
Jest throws an error, that says methodTwo has been called two times instead of one. I Figured, it's because I'm running two tests that calls class static method two times (one time in first test, and second time in second test), hence methodTwo is called two times.
So my question, is it possible somehow to isolate those tests ? When I'm running test one (calling some class method) it shouldn't influence other test results.
Thanks!
You're right, by default Jest spies keep their state through your different tests.
To reset them, I personally use :
afterEach(() => {
jest.clearAllMocks()
})
see https://facebook.github.io/jest/docs/en/jest-object.html#jestclearallmocks for more information
Context
My issue is that my ngOnInit function calls many sub functions asynchronously by using Firestore to populate a form from one document as well as subscribing to various collections to display lists of data. This gets very tedious to test as I then have to ensure things are subscribing properly and values are being returned in the correct order.
According to the Angular docs:
However, it's often more productive to explore the inner logic of application classes with isolated unit tests that don't depend upon Angular. Such tests are often smaller and easier to read, write, and maintain.
Which in my opinion would make preventing the ngOnInit (through a spy) a valid way of
Isolating tests and
Reducing complexity when initializing the tests
You can then explicitly call each function separately and only test what is needed, instead of having ngOnInit calling the functions (and possible you as well).
This is exactly how I have gone about it so far:
Component
ngOnInit() {
console.log('init');
this.setup();
}
setup() {
this.firebaseService.setup(_ => {
this.checkNewOrEdit();
});
}
...
Spec
...
beforeEach(() => {
fixture = TestBed.createComponent(Component);
component = fixture.componentInstance;
spyOn(component, 'ngOnInit');
fixture.detectChanges();
});
it('should be created', () => {
fixture.whenStable().then(_ => {
expect(component).toBeTruthy();
});
});
it('should call checkNewOrEdit() on setup()',
() => {
spyOn(component, 'checkNewOrEdit');
callbackValue = 'success';
component.setup();
expect(component.checkNewOrEdit).toHaveBeenCalled();
}
);
Which is working fine for me so far.
Is this an acceptable solution? Or are there better ways of going about this?
I had problems further down the line in terms of variables not being set previously.
This solution does work but it just adds further complexity further down the line. I would recommend solving the problem initially, then you don't have to worry about how and where variables where set later
I solved my problems by editing my firebase mocks to include all the collections and docs I subscribe to throughout the initialisation chain. I think I must have forgotten a a doc or collection somewhere.
I am extremely new to testing in general, not just React testing, so I'm still trying to figure out not only how to test, but what to test.
Here is my login callback that gets called on submit of a form:
login() {
let typedUsername = React.findDOMNode(this.refs.username).value;
if (!typedUsername) {
return this.setState({
errored: true
});
}
// we don't actually send the request from here, but set the username on the AuthModel and call the `login` method below
AuthModel.set('username', typedUsername);
AuthModel.login();
},
AuthModel is a Backbone.js Model. But for this question, lets just say it's an external module that I'm importing into my Login.jsx component.
I'm writing a test to see that if a username is typed, then AuthModel.login() gets called. Do I want to test that in my test.Login.js test file, do I test that in my test.AuthModel.js test file?
it('calls login when there\'s a username present', () => {
React.findDOMNode(LoginElement.refs.username).value = 'foo';
TestUtils.Simulate.submit(form);
// not sure which direction to take this test
});
Current test (in test.login.js) for context...
Any advice is appreciated, as like I said, this is genuinely the first testing I've ever done.
Sounds like you want a spy. This will stub out the real implementation of AuthModel and replace it with an object you can make expectations against:
spyOn(AuthModel, 'login');
// trigger login somehow
expect(AuthModel.login).toHaveBeenCalled();
Or if you also want to test the arguments which are passed:
expect(AuthModel.login).toHaveBeenCalledWith('username', 's3cretpassw0rd);