Skip to content

Instantly share code, notes, and snippets.

@dvallin

dvallin/blog.md Secret

Created November 29, 2017 16:46
Show Gist options
  • Save dvallin/142cea2db81142352cccfce2223dbdf9 to your computer and use it in GitHub Desktop.
Save dvallin/142cea2db81142352cccfce2223dbdf9 to your computer and use it in GitHub Desktop.

Harry Potter and the Javascript Fatigue

These pages give a snapshot of my current understanding of what a modern frontend technology stack might look like. At the time of writing, it is about two years after the time when everyone was talking about javascript fatigue and yes my team was no exception. We were fatigued by the ever growing complexity in the React ecosystem and had to learn a lot of things the hard way. Though, things have gotten much easier since then, writing a frontend is still very complicated and hard to get right. Especially, if you want it well tested, scalable onto a larger team and maintanable even if design decisions and libraries change.

This text cannot achieve all of that. But it can at the very least present recipes for building and testing a Vue.JS frontend with modern state handling and side-effecting. And more importantly, it is written as Harry Potter fanfiction!

Part 1: The Philosophers UI

You are in the first year of Hogwarts and decide - as you have been raised by geeky muggles - that it would be a great idea to write an app to track all your magical homework. You call it TodoMVC (the M stands for magical)! Over the course of the next few days you try out a lot of UI frontends and conclude that Vue.JS is the winner as it is the only UI frontend magical enough to run on campus.

Setup

So you install the Vue.JS cli and use it to create a webpack + vue.js project

mkdir todomvc
cd todomvc
vue init webpack frontend
? Project name frontend
? Project description Todo MVC magical edition
? Author Maximilian Schuler mschuler@itemis.com
? Vue build runtime
? Install vue-router? No
? Use ESLint to lint your code? No
? Setup unit tests Yes
? Pick a test runner jest
? Setup e2e tests with Nightwatch? No

(You can pull the App from here the commits are roughly aligned to the chapters of this Tutorial, but only roughly)

In your project folder you run yarn install to pull some dependencies and yarn dev to start a hot-loading web server for development. In another console you start jest --watch for your interactive testing console.

The first component

At Hogwarts you learned that you can start with a definition and things pop into existence all by themselves, so you start with your app by writing a single unit test

https://gist.github.com/7625b733fedbc44f84798d45c59e5745

This test first imports the component to be tested and a model of a task. Both have not been written yet and to your surprise the test did not bring them into existence out of thin air. But you have a pretty good idea now on how they should look like. The item should take as props a task object that contains a title property and it should render it.

Now that you specified everything you need to write the missing parts. And with everything you already know you can easily create the model

https://gist.github.com/08c7f45a37f809081ab3aa478aa14ab6

and the component

https://gist.github.com/9b692be724b3637aa88d8c11a5ce0f41

the test worked like a charm.

From one to many

For a list of tasks you end up with the following test

https://gist.github.com/ad674e4a22aba65604f001858347c1e4

this time you could even verify that the the list correctly fills its sub-components. The shallow rendering from the vue-test-utils is very helpful for exactly this purpose. The component gets rendered to html but not its sub-components. This keeps the snapshot tests more succinct and enables you to find the components and check their props. This is a Todo List you could end up with.

https://gist.github.com/8f9ca9abab521e65a580867e3b321d50

The v-for directive is the used to iterate over a collection of things, like tasks and the :task="task" is an expression binding the current task to a prop named task.

Being in style

The todo list still looks somewhat ugly, so you install bootstrap yarn add bootstrap-vue bootstrap@4.0.0-beta.2 and include it in the main.ts

https://gist.github.com/5f2f42882f5dc335566f50bb7d81de5e

Now the bootstrap markup is available everywhere.

https://gist.github.com/f851a16197c961d14158446a8bdada9a

You are reasonably happy with your app now. It renders your tasks and does not give instant eye cancer to every passerby! The next thing is to build mechanisms to also change the todolist, because manually applying the proper spells is awkward and some of them have not been introduced in data manipulation magic yet.

Part 2: The Chamber of Storage

Now that you have a frontend that allows viewing of a static list of Todos, you need something to store and change all your magical tasks. You imagine a chamber of secrets. But after some consideration you decide to call it a storage of state, or shorter: store. As it turns out these things already exist (what a relieve). You could use vue.js directly to manipulate state but there is also vuex.

Store Skeleton

So you install it yarn add vuex and put it into use

https://gist.github.com/0a763b54ea54b3bfe687d4522ec7db73

with a store that just contains two tasks

https://gist.github.com/e2c409a2a9dec03280a56ce7b3929349

Unit testing user workflows

Now this was easy, but does not really solve your problem yet. You need a way to enter the title text of a new task. An input tag should be visible and on hitting enter it should call an Action that in turn mutates the state. You start by writing a test for a new vue component with an input field.

https://gist.github.com/7d63045f49dcda6d2dd4b2f4b37bc7d9

This test just asserts that there is an input field. The component that may look like

https://gist.github.com/1dc17fe200168278e5727a069310214f

This is just a component that renders an input field with no functionality at all. You want it to add a task to the store once you hit enter. This can be tested by simulating akeyup.enter event and and that it calls a method called addTask.

https://gist.github.com/1c735e3091cf01c298ba4d4cd658442a

To make this test work only two very small changes to the component are necessary.

  1. add @keyup.enter="addTask" to the input field
  2. add a addTask() {} method to the methods block of the component

Ok, this calls a method on the right key press. But you did not specify what the method should do! So you define that it should dispatch an Action to add a task with the given title.

https://gist.github.com/d76d0da36c8d6dbd8b51dc9f97e3c5fe

This test assumes that the field taskTitle contains the input fields value. But wait a minute, that is not what you want to test! You just want to capture the bevhaviour of the component and not it's implemenation. A perfect test would

  1. set the input fields value to 'title'
  2. trigger a 'keyup.enter' event
  3. check that the correct value has been passed to the store

After some experimentation you finally come up with the following test

https://gist.github.com/ff9381be7e2db50eb73cc821fee5ad8e

So you spy on the actual store (0). Then you set fill the input field with a test valuer (1). To get that value into the components state you trigger an input event manually. This would normally be done by the browser. Now you trigger the key press of the enter button (3) and finally check that the correct Action has been triggered in the store.

In step (4) you give vue js a tick to sync with the values in the input field (if the implementation does not use watchers this might not be necessary, but better be safe than sorry). And after asserting that the store has been called you have to report to jest that the test is done (5).

This test can replace all the others. In the process of developing a component it is Ok to test implemenation details like internal function calls and internal variable. But once the component is finished, the test cases must only the bevavior that is intended and not the implementation. Or else the next time someone changes one implementation detail or refactors your component all your tests break.

Anyway, here is the full component:

https://gist.github.com/3ee3eaec3adfaea2912d21810a4dd7ed

Testing store interaction

Up until now, you have not implemented the AddTask Action, which actually resulted in a console error

https://gist.github.com/3abdd649c09412d014410aa42e5f399b

It probably did not fail your tests. But this is the next thing that needs to be implemented.

First you need something to change the state. In Vuex this is called a mutation. A mutation is very easy to test, as it is just a function that changes state with no strings attached.

https://gist.github.com/1a1894d1c77029c91d010a9099fde64f

This is pretty easy to implement also

https://gist.github.com/ab173c3dfc41b0e6514fbfe42380e3e9

The Mutations name is in the Past Tense to emphasize that it describes an event that has already happened. The idea is that this event has not been accounted for in the state yet. This also means that there must not be any circumstance in which a Mutation might fail! A Mutation might be TaskAdded but also TaskAddedFailed. The second mutation describes a failed attempt, the mutation itself must not fail however!

The next thing you want in a store is a predefined function to fetch a task by title.

https://gist.github.com/70c22edaa4da3254d8d00be4579b0314

https://gist.github.com/3577bcc7733cc86568a1df3c5389dc69

Testing this getter was easy, but getters might very well refer to each other, consider this:

https://gist.github.com/85da6d68f3aebcc58cbaf0cd22015838

First title return the first task of the tasks ordered by title (this code uses lodash). In such a case it makes sense to mock the dependencies

https://gist.github.com/108d4afa63c64cdaef3bf1ae1fe7c507

On first glance, testing actions is much harder than getters or mutations as actions have much more dependencies and their effects are function calls not modifications of the state. But that also means that you do not have to think about how the state changes! It is perfectly valid to mock the context (state, getters) and spy on the commit function. You just define a constant state and mock away the getters. You can then assert that the action calls the right functions. The only thing that might get complex is that Actions are allowed to do asynchronous operations and all kind of messy Api calls. (And we will look at that in Part 3)

https://gist.github.com/613e2a13d03e310e2c636f767b65e308

Spying on a function is a very powerful tool to verify a lot of interaction patterns. The above code says that it only adds a task if it cannot be found by title. And using the getter you already implemented this function is just:

https://gist.github.com/b7fc96ddbbb1c43d4a2156ef591c4c1d

A scalable future

For today you are satisified with your result. You can view and add tasks! The next thing you want is to change the state of your Tasks. Or even have multiple Todolists so your Quiddich and Alchemy tasks don't mix anymore. You even think about Priorities and tasks that can be assigned to people and estimated and attached with obscure outlook files and synced with jira and your current horoscope. You quickly sketch some types for your Task State logic

https://gist.github.com/d40664feee9f258371e6acc22bae9536

now with these actions and mutations you should be able to implement this in a test-driven way.

https://gist.github.com/75e61d1385171a069c03b48dee8f5362

You realize how well this approach scales. If you could only convince the Weasleys to learn Typescript to help you out with the new features. But this is something for another day at Hogwarts!

Part 3: The Prisoner of Api

Your Todos can be created and modified now but one major draw-back is that all state is lost after restarting. You feel that having to apply a reappeariamos spell is a huge time-waster so you ponder different solutions. Your idea is to banish the todos to a place like Azkaban. To imprison them temporarily in a basement of data, a database ment. You find such basements in the internet and after some consideration you decide to give either Cassandra or MongoDB a try, but you would never use Voldemort. Never.

The deus ex machina backend

Because a wizard does not submit himself to backend work, you ask your machine-elf to write some backend for you. And he quickly comes up with a Reactive Repository

https://gist.github.com/503a4d9ddb9fab9604f12a4cd0de4680

The little creature defines an Id on each task

https://gist.github.com/7a288e74d1db083a99b42face1101d72

The elf tells you something about Command Response Segregation and how Hexagonal Architectures is the greatest discovery since coffee mugs.

https://gist.github.com/a9564ee50a0e81d5f584cdd9b2fbb80d

He talks and talks. Now he mentions reactive backends and how reality is just a stream of past events in a message queue. You start to pity him and his simple elf-mind

https://gist.github.com/0e57eb4f963dcf2915cf8892f71127e7

He finally starts to make sense when he talks about the Rest Api and that he will just have a Get and a Post endpoint to fetch all tasks or post a new one.

https://gist.github.com/f6d3751bc7725b8dce4cdac00f98c60e

Asynchrounus testing

So the only thing to do is writing an api that correctly calls the backend and updates the state of the store. That should not be too hard to do. First you have to install a libary to make calling the backend a bit easier. You let yarn do the magic: yarn install axios. And now you describe the api. You want it to fetch tasks und unwrap them from the more technical Axios Response. Also if an error occurs it should be handed down to the caller.

https://gist.github.com/3f27b7eab8c8118ce83c7e38df6c2e5e

These two tests also use Jests async testing tools and mock away the axios.get method using jest! (Note they are not anotated as async, but return a promise!)

The api itself is pretty easy to implement now.

https://gist.github.com/f5a1dddae6ac2951c13cf3944309c8a8

Now that the Api is defined, it makes sense to write tests for the Actions. Actions do not change the state, they just invoke mutations, as you saw in the previous Part. So this is what you define here.

  1. If the Api succeeds, it calls a FetchTasksSucceeded Mutation
  2. If the Api fails, it calls a FetchTasksFailed Mutation

You just need to mock the fetchTasks method of the Api and have it return Promises. After looking at the vuex documentation again you even find out that such promises can be directly returned by Actions, so you can even await it from your testing code!

https://gist.github.com/d6ed70e4cec436b6bbd326e6d6997d96

Compared to the version that returns a promise this is more succint. You can await any promise and then assert things.

Implementing the Action is again pretty much straight-forward.

https://gist.github.com/ef269c7f15d7a7c0b33b44546169816e

This was - to your surprise - less painful than the Herbology courses at Hogwarts. But only slightly.

Posting data

Fetching an empty list from a database ment is not of much use. You have to fill it somehow. So you will use the POST endpoint the elf defined and write your api function.

https://gist.github.com/3c26c8852a3d1eb875c9a29023250507

https://gist.github.com/a143db824a1274f0c685c0213ff348c5

This is exactly the same as for your fetching endpoint. But you did not specify that the data is handed down to axios in a specific way. Adding an additional expect statement might make sense

https://gist.github.com/889e3b16c3908d4747e626859399a96e

Here you could also specifiy that the correct request headers are set. The downside about these tests is that they heavily depend on the library used. But this goes for all tests against the backend. Idealy, we would just create a mocked endpoint using something like wiremock, but I do not know a reliable method to use such tests in the frontend (yet).

Creating the Post action is simply enhancing the AddTask action by calling the api.

https://gist.github.com/5761d5efe2d6fb15b90ca2dc6e97a0f2

Your Action became asynchronous but not much changed besides that. The Action itself is just an api call

https://gist.github.com/b72175413c68753db1316f5eca132828

You do not have to create a Task object anymore, you just get one from the Api and it already contains an ID.

A type casting Api

Wait it contains what?

The Task object you receive from the Backend contains an Id field (you vaguely remember the machine-elf mentioned that), but your Task model in the frontend does not have such a thing. You realize that all the type-safety in your backend calling expressions like

https://gist.github.com/4e23c8bfa02a6254608f5ae1b5c8e18b

is nothing more than a compile time illusion. You do not like illusions, you only like actual real magic. Talking to a backend that might give you any object it wants to. Reality is outrageous! You have to do something about it! (And now you can appriciate that you decided to put the api in its own layer) What you want is this

https://gist.github.com/7d765630ef5c16813775ab1a99c25a0c

All the fields you do not know shall be removed from the result and each object must be mapped onto the right types in the api. That is the wizards' way.

First you add the id to the Task

https://gist.github.com/f7ebf5b4cae435ade1a48d54f929e526

and then you just put another function into the api that casts your objects onto the right type

https://gist.github.com/1e8353936f20ccee7978e6eb098734b1

You do not pretend anymore that you already get the correct type from the backend, you explicitly map to your own type.

Casting Refactoriamos

All these Promises with their catch-then-resolve-reject semantic are very verbose and hard to read. You just learned the Refactoriamos spell at Hogwarts and thought you could try it out on your api code.

https://gist.github.com/1690fb542b5e383be41edafa19024dd1

This is all that remained after the spell evaporated many of your lines. And all tests are still running green! What a great spell indeed. By making all functions async, it removed the helper function completely and lifted your code out of the monadic mess, that is Promise handling. You cast the spell again, this time on your AddTask action and you get

https://gist.github.com/20051d4c07188925208067b8ae4f84f8

On first glance, the spell did not do much. But you can see that it got rid of the chain of thens and the catch function somewhere. You have a nice try catch block instead. You apply the spell to your other actions in the same way. And feel more confident with your codebase.

You also cast the spell on some of your tests, they stop being so hideous

https://gist.github.com/da421772ddc720b5783c9d764a41e1d8

again instead of creating Promises you just await a result and use it.

There is also vuex-saga based on redux-saga that takes the idea of making promises more imperative even further. However, by the time of writing vuex-saga has a bug that is a complete show-stopper for people who want to handle errors (like everyone).

That's it for now. You feel relieved. Finally, you can think about the important topics of web development. Like what color you want for important todos, and how many pixels of border radius are necessary for your textboxes to impress Ginny Weasley.

/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment