As a developer working on a compiler-driven full-stack web framework like Wasp, you know that testing is crucial to ensuring your app's reliability and stability. With Wasp, we take configuration and source files with unique logic and generate the complete source code for the web app. In this article, we'll dive into our approach to testing, highlighting how we keep our tests readable, descriptive, and effective.
Our Approach to Tests
At Wasp, we believe that test code deserves the same care as production code. Bad tests slow you down and make you afraid to change things. So, our principle is simple: if a piece of test code matters enough to catch a bug, it matters enough to be well-designed. We refactor it, name things clearly, and make it easy to read and reason about.
Tests That Explain Themselves
Our guiding principle is that tests should be readable at a glance, without requiring an understanding of the machinery hiding underneath. That's why we write tests so that the essence of the test, the input and expected output, comes first. Supporting logic and setup details follow afterward, only for those who need to understand the details.
Courage Not Coverage
Chasing 100% coverage is fun, but it can push you to spend time testing code paths that don't really matter. Instead, we aim for "courage" – confidence that if something breaks, we'll know fast. Our combined tests catch nearly all meaningful errors, and we're not afraid to refactor or add new tests as needed.
TDD (But Not the One You Think)
We've always liked the idea of test-driven development, but it never really stuck for us. Instead, we start coding and only after something works do we add tests. We love strong typing (we use TypeScript and Haskell), describing what the feature should look like and how data should flow. Once the types make sense, the implementation becomes straightforward.
Testing the Compiler
At the core of Wasp sits the compiler, written in Haskell. It takes a configuration file and user source code as input and generates a full-stack web app as output. Although Haskell has excellent reliability and type safety, tests are still necessary. We use unit tests to ensure our compiler's logic is correct.
Our E2E Tests Story
The purpose of our end-to-end (e2e) tests is to verify that the Wasp binary works as expected. We're not concerned with the internal implementation; only its interface and outputs matter. The interface is the Wasp CLI, which we treat as a black box: we feed it input, observe its side effects, and verify the output.
Tracking Each and Every Change
Wasp generates a considerable amount of code, and even small compiler tweaks can cause weird changes in the output – a real-life butterfly effect. We want to be sure that each PR doesn't cause any unexpected changes. That's where snapshot tests come in – we use them to track the compiler's code generation changes in the form of golden vs current snapshots.
By following these best practices and principles, you can ensure your full-stack framework is reliable, stable, and easy to maintain. Whether you're working on a Swift app development project or another type of web application, the principles outlined here will help you write effective tests that give you confidence in your code.