1. Introduction to Form Validation Framework
Today, we're going to talk about a form validation framework that combines my passion for open-source tools and libraries with my love for Unitest. It takes the syntax and style of Unitest and introduces them into the world of form validation.
Hi everyone, I'm Evitar, I'm a Frontend Engineer at Meta, and one of my passions is working on open-source tools and libraries and frameworks for other developers to use, and today we're going to talk about one of them. Now this one, in particular, is a combination between that passion and my other passion, which is Unitest. I love Unitest so much that I actually built a unitesting framework. I mean, not really a framework, we have enough of those, but I did build a form validation framework that takes the syntax and style of Unitest and libraries like Mocha or Jest and introduces them into the world of form validation.
2. Understanding the Problem with Form Validation
In the world of form validation, the lack of structure is a major problem. When adding forms to our features, it's often unclear where and how to implement validation. This leads to validation code becoming intertwined with feature code, resulting in a messy and difficult-to-maintain codebase. When making changes to the feature later on, we have to disassemble and reassemble the code to fit our current needs.
But before we talk about the VEST itself, let's first try to understand what is the problem with form validation. I mean, we've been doing forms for 20 years now, so what is the problem? And I think there are three main problems in the world of form validation, and it all begins with structure, or the lack of really. When we write our feature and we add the forms, we don't really know where and how to put the form validation. I mean, yes, we have a change handler and it should come out from there. But inside the forms, we usually not just care about the data, we care about the feature and the user experience. And then a lot of the validation code gets intertwined with the feature code. And then we get a pile of spaghetti code, really, that's all about both form validation and user experience. And then when we come back to maintain the feature, like months later, and try to add in stuff or remove stuff or make changes, like make fields depend on one another, we have to disassemble all the feature-specific code that we wrote and reassemble it in a way that's suitable to our current need.
3. Challenges with Form Validation Testing
Forms and form validation are difficult to test, especially when it comes to complex validation logic. This leads to the most bug-prone areas of our apps not being tested, which is a problem.
And one other problem is that forms and form validation is really difficult to test. Unfortunately, forms are the interaction-heavy parts of our apps. It's where people touch, scroll, type, do everything, basically click. And that's also where the money comes from. And because this is the most interaction-heavy part of our app, this is also the most bug-prone. And unfortunately, it's really, really hard to test. I mean, everything you want to do in form validation in terms of testing has to go through all the flows. So either user event simulation or you have to go through all the data. And it's really hard to test, especially if you have some complex validation logic that you don't want to go all the way through. And we end up not testing those, and that's a bummer.
4. Types of Form Validations and Using Unit Tests
Types of form validations: functional matchers, schema validation libraries, and framework-specific UI form state validation or state management libraries. Unit tests can be used in form validation by creating a form validation suite.
But what types of form validations are out there? I mean, that cannot be the first form validation library. I mean, we've all used form validation libraries, both in the client and the server. So I'll try to get a brief overview of what's there, and then we'll understand how VEST is different.
And first we have the functional matchers, like basic functions like isEmail, isNumber, longer than, that basically give us a Boolean response if some value matches some criteria. And this is really simple and this is also really useful. But it doesn't care much about structure, it doesn't help us with maintenance, and neither with testing. And this is really useful, but not a full-fledged solution for form validation.
Second is that we have the schema validation libraries that take like a next step of what we just described. They take the form of the data, the shape of the data, and the shape of the data in its entirety has to conform to some criteria. And this is good mostly for the API level. But when talking about the client, not really. I mean, what do you do when the user interacts with a form, like types inside the username field? Do you validate the whole thing, the whole schema? And also it's really hard to describe complex ideas, like fields that depend on one another, inside of a schema. So it's really useful, but not necessarily the way we want it to be.
And third, we have in the client framework-specific UI form state validation or state management libraries that are specific to the framework and they give you components or event handlers or directives to use inside your app. And then what it does is basically take some control away from you, but gives you all the state management on the form and all the form validation for the form, and then you are pretty much set and it's good. But the problem is, well, it's framework-specific, UI framework-specific, so we cannot share between your client and server easily, or between Angular or React or Vue easily, and most of us have at least some apps or some different types of our apps. And also, because they take control from you, they put their stuff inside your app, it's really hard to take that control back when you need to bypass some behavior. It's good, but not necessarily, again, the way we want it to be.
And how do unit tests fit in? This is Test.js. A couple of years ago, like six years ago, I started writing unit tests as part of my work, and I realized this. When we write a unit test, we have our unit testing suite. That usually starts with a describe, it doesn't have to, but it starts with some top level suite that describes the idea that we're testing our feature or app or whatever it is. Inside of it, we have a series of tests that basically describe what assertion we're making now and what is the error that we're going to show in case of failure. Then we have our assertion, for example, expect and what we expect it to be. What if we could use that same idea inside of the world of form validation? I mean, it's pretty much the same. When we write a form, we have our top most concept of a form that we want to describe. Inside of it, we have the different fields, a set of fields that we want to test, and then we just want to make assertion. Now, instead of asserting behavior, we want to assert data. I played around with this specific idea, and this is what I came up with. First, we create our form validation suite.
5. Overview of VEST Form Validation
That's slightly similar to a unit testing suite. You describe, optionally, the name of the form that you're validating, and also you pass in the data from the form that the user just interacted with. Inside of it, you add, obviously, a series of tests. You have a test that's very similar to a unit testing test, only that it adds one extra field, which is the name of the field that you're validating, so in this example, Username. Then, you pass in the message that the user would see in case of a validation failure, so you do it also both for description inside of the code, and also for the user to see.