Working an application in Vue.js with TDD — An extensive guide for people who have time — part 3
Finishing our components and integrating the store
This is the third in a series of articles:
- Part 1: Setup and the first test
- Part 2: Continuing the UserView
- Part 3: Testing the store and the rest of the presentation components
- Part 4: Testing the API request service
- Part 5: Adding and testing with third-party dependencies
- Part 6: Overview — 26/11
If you want to read it in pt-BR, check it out here.
Last week we ran simpler tests related to the UserView component. The most interesting of that was the refactor we made, creating a simple base that will be used to test any component in this project.
Let us now delve into and finish all the component tests, and integrate all that with the store.
Integrating the store on our tests
First, let’s change our state file: src/store/state.js
export default {
user: {},
}
Let’s create a fixture file that will contain an example of how our real data would be to be used in our tests.
The refactor is a little long. But let’s understand a what has been done here.
With the shallowMount we are only rendering our component, without any third party dependency and any extra configuration.
Then before the refactor our component didn’t have how to use Vuex or any dependency we ended up installing inmain.js
Thanks to vue-test-utils, we have how to create a vue local instance to be sent to our component. This way, in our test we can indicate all the dependencies it uses globally, on a local way.
- On line 9, we are creating this local instance, doing exactly what it is done on
main.js
during the Vuex installation - On line 13, we create a state variable, which can be modified between the tests
- On lines 17 e 18, we send to the component our vue local instance, just like a new store (again, this is exactly what has been made on
main.js
) - On line 28, we put the
beforeEach
as it is always called between each test, I take the opportunity to “reset” the used variables on the tests for the standard value. This case, we are using our state original file and storing a copy of this file on our state variable between each test - On line 37, finally we can start to see one of the benefits of using the
build
function. Here we guarantee that before running this test we will have a resetedstate
variable to its original value. Then, we can change its value with our fixture, containing “real” data of our searched user, and ONLY THEN we would make our component’sbuild
.
This way gives us much flexibility because we can control our tests and make changes before constructing the main component to be tested.
And finally, we can remove our wrapper from the test, as on line 41 we want to guarantee our component is sending the user present on the state and not on data anymore.
Now, our test is failing, as in our UserView.vue we are not sending via props to the VUserProfile.vue our store user. Let’s fix it.
Now, we insert the mapState
and map the user
property of our store and send it to the VUserProfile.
Success! Now we are guaranteeing that the UserView is sending the correct property to the VUserProfile.
Last test of the UserView
We are with our component almost covered. It’s missing only one functionality.
I’d like to guarantee that, whenever the VUserSearchForm triggered an event submitted
that we call it a store action containing the user which was typed on it.
Let’s first focus on the test:
- First, we create a variable containing the user hoped to be
sent through thesubmitted
event - Right after, we manually emit a customized event of the VUserSearchForm component which is exactly the event it will make in the future
- With all that, on lines 50 and 51, we hope that the store
action
calledSEARCH_USER
has been called (requested) and that an object containing our user has been sent as payload - Line 51 may seem odd, but this is the form we have for guaranteeing we are sending the correct payload to the store actions, because we are not calling manually the store method
What we do is calling the store.dispatch
and the Vuex internally calls our action for us. And it ends up injecting on the first parameter an object in which we get to take properties from the Vuex. And as a second parameter, it is our payload.
Then we manually need to take the store call, getting the second parameter which is the payload (our username).
Now we can understand how the preparation for the test was made.
On line 1, we are using a very nice functionality of the jest. Basically using the jest.mock
, jest will take a file in the same directory of our imported file, and fill look for a src/store/__mocks__/actions.js
instead of the original src/store/actions.js
This allow us to create our mocks so that they can be used by the whole application.
It also works for third parts dependencies. It will basically mock all the dependency functions for you.
Then on line 8 we import our store actions which actually is the mock file we will create.
We insert the actions in the store on line 22.
And finally, on line 34 we are resetting among each test, all the mock functions to the original state, so that no test impacts on the result of the others.
Our test is failing. First, we need to guarantee we are working with a mock function.
Here we are basically returning an object with our function ofSEARCH_USER
moving back to a “standard” value, which would be a solved promise with our fixture user.
Finally, on our UserView, we can hear the events of submitted sent by the
VUserSearchForm and while calling this we can make our action dispatch.
With these tests, basically, we are covering our UserView component. 😄
For our next components, which are only presentation ones, we are going to speed off a little, as we are basically going to test the same things.
I’m going to post the test and production full files directly to save this article from being EVEN longer. But we should follow the same step-by-steps as it has been done before.
Testing the VUserProfile
Here we don’t see anything that new. As the VUserProfile is a presentation component.
Here we add only the data rendering received via props.
So then, we can see everything moving on. 😏
Testing the VUserSearchForm
Again, the tests here are very similar. There is only a piece of good news, which is the last test.
There, we insert manually in the input our searched user and then we trigger the input events, indicating that “we wrote” in the input.
And finally, the click/submit button so that we guarantee that the event was issued accordingly with the input value.
This way, you have already learned the base for testing components in Vue. Is highly likely that it will move like that and doesn’t get much more complicated (there are cases and cases 😅).
For the production code, we only render the form and on the submit we issue a submitted event with the username payload.
Testing the action
Now that we covered all our test components, we can go to the second part which will be testing our store, the actions and mutations.
There are two ways of testing the store and I’ll bring only one of them. In case you want to know more, take a look at the
’s book, Testing Vue.js Applications.Let’s start by testing our action.
In this case, we have only one action. Then we make a manual call to the method SEARCH_USER
with a mocked commit
sending our user payload.
On line 20, we have the use of a dependency which we downloaded in the beginning of the project. It guarantees that all the promises have been solved at this point.
Finally, while calling the action, we hope that our API service has been called in the function searchUser
and that it our hoped user has been sent.
Also, as we are testing only the happy path in which the request was made successfully, we hope our commit
has been called with our fixture, where it represents an answer mock of the Github API.
// src/api.js
export default {}
As we are using the jest.mock('@/api.js')
we need to create our mock file. Later one we are going to create our request service but for now, for our tests, we can have our production file only as the export default.
Not to make our article even longer, I’m going only to test the happy paths. We are not going to cover alternative ways here, e.g. a request fail, network error, etc.
Well… we have just arrived at the phase:
Basically, our method still doesn’t exist. We can finally make our SEARCH_USER
production implementation.
We can make the production code in many ways. In this case, I opted for returning a promise.
And here, we make everything we are demanding in our test. First, I’m calling our api.searchUser
, sending the username which is being used as a parameter.
Right after I have the answer, I’m making the commit of a mutation, sending our user and returning the API service.
Perfect! All the tests passing.
Testing the mutation
The mutation must be the simplest case we have so far.
Here, there isn’t any secret at all. Before each test, we reset our local “state” and what we are doing is calling our mutation directly, sending this state, passing our user.
In the end, we hope that the state had been assigned as being our user, and here I want to make sure that it is a pure function. So, I want to guarantee that the state value is a copy of the original user.
Now that our test failed, let’s go to the implementation.
Done! I know that in the case of Vue.js I shouldn’t worry about making a copy of the object, the way it deals with reactivity, but I personally like to keep my mutations as pure functions. There wouldn’t be any problem if you do something like: state.user = user
.
Just don’t forget to change the test in case you want to do this way.
Our test has passed 😄
Overview
In this third article, we did:
- Completed all component testing
- Integrated the tests with the store
Stay tuned to next week we will be finishing our component with store integration.
Thanks for the attention, if you liked, please click on the 💚, and any questions, suggestions or corrections feel free to send me a message, I thank you very much 😄.
My Twitter: @DKuroski
See you next week 😄