Javascript

Unit Testing Qwik Components

Jan Kaiser
7 min read
Unit Testing Qwik Components
This article was written over 18 months ago and may contain information that is out of date. Some content may still be relevant, but please refer to official documentation for the latest information.

Unit Testing Qwik Components

Qwik is a new, superfast JavaScript framework from builder.io. Created by Miško Hevery, the author of AngularJS, Qwik aims to deliver instant loading web applications of any size or complexity through resumability. This is accomplished partially by delaying the execution and download of JavaScript for as long as possible while providing an excellent developer experience.

"You know React? You know Qwik."

qwik.builder.io

Given Miško's background as the creator of AngularJS, it's interesting that writing Qwik feels very similar to writing React. It even has a React compatibility mode. Personally, I have experience using mostly Angular, but I found that developing with Qwik was very smooth and enjoyable.

So, although I am definitely hesitant to jump on new technologies, after attending Miško's workshop, browsing through all the resources at framework.dev tinkering with Qwik, and building a couple of apps with it, I became a fan.

However, being a responsible software developer, I then wanted to add unit tests to my Qwik app. And because Qwik is relatively new, I found out there was little documentation on how to do this. Furthermore, until recently, there were no tools to easily set up and interact with Qwik components in unit tests. But that didn't stop me. I explored, asked, and waited for a PR to be merged, and now I am proud to present a comprehensive guide to unit testing Qwik apps. So, without further ado, let's dive into setting up our testing environment.

Configuring a Testing Environment

There are several options when it comes to choosing a testing framework, and I have even experimented with using Jest, as it is the popular choice for unit testing. However, since Qwik uses Vite, I found that using Vitest was the most straightforward and efficient option. Vitest is specifically designed to be the go-to test runner for Vite projects, which makes it a great fit.

If you are familiar with Jest, you will be happy to know that the Vitest API is very similar, so you won't have to learn much to use it effectively.

Before moving further to the actual setup, let's make sure you have a Qwik project to test. If you don't yet, you can simply create one by running npm create qwik@latest.

Now, let's set up vitest in our Qwik project. It takes 3 easy steps:

  1. Install Vitest as a dev dependency by running npm i vitest --save-dev from the root of your project. This will update the devDependencies array in your package.json file.

  2. Update your vite.config.ts file to include a test configuration in the defineConfig function. You can start with an empty object for now, but you can always add configuration options later on. Your vite.config.ts should look something like this:

// ./vite.config.ts
import { defineConfig } from 'vite';
import { qwikVite } from '@builder.io/qwik/optimizer';
import { qwikCity } from '@builder.io/qwik-city/vite';
import tsconfigPaths from 'vite-tsconfig-paths';

export default defineConfig(() => {
  return {
    plugins: [qwikCity(), qwikVite(), tsconfigPaths()],
    preview: {
      headers: {
        'Cache-Control': 'public, max-age=600',
      },
    },
    test: {}, // this is the config entry we are adding
  };
});
  1. Update the "scripts" section of your package.json file to include commands for running vitest once, running it in watch mode, and for generating coverage reports. Your "scripts" section should look something like this:
"scripts": {
    ...
    "test": "vitest --run",
    "test.watch": "vitest",
    "coverage": "vitest run --coverage"
}

After completing these steps, you should be able to run npm run test and npm run coverage from your terminal to use Vitest.

Note that if you run npm run test and you don't have any tests yet, which is likely the case at this point, you will see the message No test files found, exiting with code 1. Don't worry. We are about to add some tests shortly.

Writing Tests

By default, vitest will look for files with names that match the pattern '**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}. In a Qwik project that uses TypeScript and returns JSX elements, it makes sense to use the *.spec.tsx or *.test.tsx pattern. I prefer to use the.spec suffix. But .test works just as well, and is equally valid. Feel free to choose the one that you like best.

To proceed with writing a unit test, we will need a component to test. For this example, we will use a simple counter component that has a div that displays a number and a button that increments it. To create the component, follow these steps:

  1. Go to the src/components directory, and create a new folder called counter.

  2. Inside the counter folder, create a new file called counter.tsx and add the following code:

import { component$, useStore } from "@builder.io/qwik";

export const Counter = component$(() => {
  const state = useStore({
    count: 0,
  });

  return (
    <>
      <button class="increment" onClick$={() => state.count++}>
        +
      </button>
      <div class="count">{state.count}</div>
    </>
  );
});
  1. Next to the counter.tsx file, create a new file called counter.spec.tsx file next to it. This file will contain the unit tests for the Counter component.

Now that we have our component and our test file, we can proceed to write our unit tests. Let's open counter.spec.tsx and add some boilerplate along with a simple test to verify we have everything set up correctly:

import { describe, expect, it } from "vitest";
import { Counter } from "./counter";

describe("Counter component", function () {
  it('should assert true', async () => {
    // this should always pass
    expect(true).toBe(true);
  });
});

We have a describe method that groups related tests together (in our case, we're grouping all the tests we will write for our Counter component), an expect method which is used to make assertions about the values returned by the code being tested, and an it method that defines an individual test.

If we now run npm run test, we should see the following output:

 Test Files  1 passed (1)
      Tests  1 passed (1)

Writing a Simple Test Case

Now that we verified that our dummy test work, let's actually test our component's behavior. First, we want to test that it renders correctly, so we will remove our dummy test and leverage the new createDOM method from @builder.io/qwik.testing to render our Counter component inside the test:

import { createDOM } from "@builder.io/qwik/testing"; // import the createDOM method
import { describe, expect, it } from "vitest";
import { Counter } from "./counter";

describe("Counter component", function () {
  it("should render", async () => {
    // create the component's DOM and get back the container and a render method
    const { screen, render } = await createDOM();

    // call the render method with the JSX node of our Counter component as a parameter
    await render(<Counter />);

    // get the div that displays the count from our container
    const countElement = screen.querySelector(".count");

    // assert the displayed count is "0" which is the default value
    expect(countElement?.textContent).toBe("0");
  });
});

Rerunning the test should result in a pass if we did everything correctly. Now we know our component does render and displays zero as the count.

Testing Interactions

"Great," you say, "but I want to test the component's logic, not just the initial render!". Well, you're in luck because the createDOM method returns one more property - userEvent - that you can use to interact with the component's DOM. This allows us to simulate user interactions, such as clicking a button, and test whether the component responds correctly.

For example, we can test whether our counter correctly increments the count by clicking the button. Here is the updated code example:

  it("should increment on click", async () => {
    // we retrieve the `userEvent` method along with `screen` and `render`
    const { screen, render, userEvent } = await createDOM();

    // render the component
    await render(<Counter />);

    // get the div that displays the count from our container
    const countElement = screen.querySelector(".count");

    // assert the displayed count is "0" which is the default value
    expect(countElement?.textContent).toBe("0");

    // pass a selector that matches the increment button as a first parameter 
    // and the name of the event we want to trigger ("click") as the second parameter
    await userEvent("button.increment", "click");

    // assert the displayed count is now incremented from 0 to 1 
    expect(countElement?.textContent).toBe("1");
  });

With this additional test, we can be confident that our Counter component not only renders correctly, but also updates the count as expected when the increment button is clicked. Voilà, our component's behavior has been verified!

Mocking Qwik Hooks

So far, we have a test that verifies the component's logic, which is great! However, there may come a time when we need to mock parts of the component's logic to test certain scenarios. Unfortunately, here comes a little trade-off for the sake of Qwik's quickness. The component$ wrapper does not expose the internals of the component, so we cannot easily modify it for our tests.

Fortunately, the most common thing we need to mock in Qwik components are hooks such as useLocation() or useStore(). We can do this by using vi.mock. As we can read in the Vitest docs, the vi.mock method takes two arguments: the path to the module that we want to mock, and a function that returns the mocked module.

For example, if we want to modify the initial count in our useStore() hook to be 1, we can mock the entire @builder.io/qwik module and return the actual module with the modified initial value. We can use the JavaScript bind method to do this. The call to vi.mock can be placed anywhere in the code as it is hoisted and will always be called before modules are imported, but for clarity, we will put it in a beforeAll block:

import { createDOM } from "@builder.io/qwik/testing";
import { describe, expect, it, vi, beforeAll } from "vitest";
import { Counter } from "./counter";

beforeAll(() => {
  // mock useStore to start with count of 1 instead of 0
  vi.mock("@builder.io/qwik", async () => {
    const qwik = await vi.importActual<typeof import("@builder.io/qwik")>(
      "@builder.io/qwik"
    );
    return {
      ...qwik, // return most of the module unchanged
      // leverage bind to set the initial state of useStore
      useStore: qwik.useStore.bind("initialState", { count: 1 }),
    };
  });
});

describe("Counter component", function () {
  it("should increment on click", async () => {
    // we retrieve the `userEvent` method along with `screen` and `render`
    const { screen, render, userEvent } = await createDOM();

    // render the component
    await render(<Counter />);

    // get the div that displays the count from our container
    const countElement = screen.querySelector(".count");

    // assert the displayed count is "1" - the default value set by our mock
    expect(countElement?.textContent).toBe("1");

    // pass a selector that matches the increment button as a first parameter 
    // and the name of the event we want to trigger ("click") as the second parameter
    await userEvent("button.increment", "click");

    // assert the displayed count is now incremented from 1 to 2
    expect(countElement?.textContent).toBe("2");
  });
});

Mocking the useLocation hook is even simpler, as it is not expected to change during a test. In this case, we can directly return an object from the mock. Here is an example of how to mock the useLocation hook:

vi.mock("@builder.io/qwik", async () => {
  const qwik = await vi.importActual<typeof import("@builder.io/qwik")>(
    "@builder.io/qwik"
  );
  return {
    ...qwik,
    // return a hardcoded object for every useLocation call
    useLocation: {
      params: {},
      href: "/mock",
      pathname: "mock",
      query: {},
    }
  };
});

Conclusion

As a newcomer to the world of JavaScript frameworks, Qwik offers a unique approach to building fast, efficient web applications. However, we shouldn't forget about the quality and robustness of our code. In this blog post, we provided a comprehensive guide on how to set up and write tests for your Qwik components.

Setting up a testing environment for a Qwik app is relatively straightforward. Qwik uses Vite as its build tool, which means that the most efficient option for unit testing is to use Vitest, a test runner specifically designed for Vite projects. We showed how to install Vitest and update the Vite config file to include a test configuration, and went through examples of how to write tests for Qwik components, including how to mock hooks and test interactions with the DOM.

By following the steps and examples in this post, you can easily add unit tests to your Qwik app, and ensure that your components are working correctly. This will give you confidence in your code, and allow you to build more complex and robust applications with Qwik. And if you found this article a bit overwhelming, do not worry, you can check out the Qwik starter kit which includes examples of already implemented tests.

Have fun writing Qwik and reliable apps!

About the author

Jan Kaiser

Jan Kaiser

Senior Software Engineer

Software engineer passionate about everything web-related. Particularly loves Angular, SCSS, TypeScript, soccer and dachshunds

Keep reading

View all posts →

Announcing Qwik for starter.dev

Hello there! Today, I'm so excited to introduce our new Qwik starter kit, which comes with 4 different technologies configured, and is ready for use. These technologies are: Qwik City, Storybook, Tailwind CSS, and GraphQL. In this short article, we will talk about the core framework, the routing, example apps to get you started, the tools and technologies used in this kit, and how it is a great out of the box starter for your next Qwik project. Qwik Qwik is a new JavaScript framework by Misko Hevery, creator of Angular, for building frontend browser applications. The major benefit of Qwik is its performance optimization, which features resumability and lazy loading. Today we will be focusing on the Qwik framework starter kit. Technologies Used in the Starter Kit Qwik City The kit is configured to make use of Qwik City, which offers directory based routing (you don’t have to worry about colocation) and much more. Qwik City is a meta framework of Qwik. It brings an opinionated and performant way to build sites at scale. Storybook Storybook is a frontend workshop for building UI components and pages in isolation, testing, and documentation. It is open source and free. It is a great tool for testing and visualizing your components in different states. Qwik currently doesn’t have out of the box support for Storybook, but we have figured out a way to configure it for Qwik. For more information on how we set up Storybook in Qwik, visit here. Tailwind CSS Because Tailwind CSS is one of the most loved CSS libraries, we also included in this kit. Tailwind CSS is an open source CSS framework. There is no special configuration to use Tailwind CSS in your Qwik app. You can use module CSS, or follow along with the kit (i.e we export the CSS classes as a variable). We will still achieve the same result, and make our code a bit cleaner, unlike when we write the CSS classes in the JSX directly. GraphQL GraphQL is another great tool that was added to the kit. This gives you an out of the box configuration to start making API calls to your GraphQL backend. GraphQL is an open source data query and manipulation language for APIs, and a runtime for fulfilling queries with existing data. How to Get Started To get started, visit the Qwik starter.dev page for all the steps needed to create a new application with our starter.dev CLI, and run the application on your local system. Example Apps in the Starter Kit In this starter kit, we have two main components; Counter Greeting In the Counter component, we see how we can utilize the useStore hook provided by Qwik to store our counter state. In the Greeting component, we made use of the GraphQL config in the starter kit. The app sends a query request with the value entered in the input field, which is then returned back as a response from the API. With all of these configurations and code samples, we believe that with this Qwik starter kit, you can start building your production scale application. Conclusion Qwik is still in its early days, and we will continue to update as we learn more. We welcome everyone to take a look, and if you have any questions or run into any trouble feel free to join the discussions going on at starter.dev or on our Discord....

Chinedu Okpala3 mins
Qwik

Utilizing Browser Storage to Enhance User Experience in a Qwik Application

Introduction As front end developers, we are always looking for ways to enhance the user experience of our applications. One of the ways we can achieve this is by utilizing the browser storage to store data, and implement some form of caching. Our web browser has provided us with different storage options to store data such as cookies, localStorage, and indexDB. In this article, we will be looking at how we can use storage to enhance a Qwik application. We are going to also explore hook technology in implementing this. Hooks 🪝 We will be extracting our storage function into a hook function that we can use in any part of our application. What are Hooks? Hooks are JavaScript functions that manage a component’s state and side effects by isolating them. That means we can isolate all the stateful logic into a hook and use it in any component. And just like in ReactJS, Qwik also allows us to create our own custom hooks. Project Set Up To get started, we need to create a new Qwik app. We can do this by running the following command in our terminal: You can also get additional tools and configurations using our starter.dev kit by running the command in your terminal: After the project is created, we can run the following command to start the development server: Now we can open our browser and navigate to http://localhost:5143 to see our app running. The demo app we will be building is a simple form with an input field and a button. The input field will be used to enter a value, and the button will be used to reset the form and the storage state. We will also see how we apply this functionality in our Qwik GitHub showcase app to persist/catch data we fetch from the GitHub API. Storage Hook After the project is created, we can create a new folder in the src folder and name it “hooks”. In the hooks folder, we will create a new file and name it useLocalStorage.ts. In this file, we will create our useLocalStorage hook. Let me explain what is happening here: The useStore hook is used to store the value. We initially set this value to the initial state value passed to our custom hook. In the useClientEffect$ hook, which runs on the client, we try to update our store value with the value from the localStorage based on the key passed to the hook. If the value is found, we set its parsed value to the store value. If not, we set the store value to the initial state passed to the hook. We also catch any error that might occur, and set the store value to the initial state. We also have a setValue$ function that is used to set the value of the store and also save it to the localStorage. The setValue$ function is wrapped in a $ function, which is used to create a QRL function a Qwik optimizer marker function. For more information on QRL, visit the docs. We also return store and the setValue$ function. This exposes them to be consumed in our components. We don't want to return the value of the store or else it will lose its reactivity. Form Component In the components folder, we will create a new folder called form, which will contain an index file that will export our Form component. The form will be expecting two props; value and setValue, which are from the useLocalStorage hook. In our index file, we will import the useLocalStorage hook and the Form component, and our updated index file will look like this: We can now see what our simple app looks like in the browser, and we can interact with it. By entering a value in the input field and reloading the page, we can see that the value persists. Next, we want to implement this hook in our Qwik GitHub showcase app to persist the gists data we fetch from the GitHub API and persisting it in the localStorage. We transform our fetch gists logic from this: to this; So we check if we have cached gists in the Storage. If we do, we set the store data to the cached gists. If not, we fetch the gists from the GitHub API and update the store data with the response. We also pass the setGists function to the updateGists function, which is the setValue$ function from the useLocalStorage hook. This function is used to set the value of the store, and also save it to the localStorage. Conclusion In this article, we were able to understand why we need to improve user experience with a focus on persisting data. We also saw how to create a hook in a Qwik framework, and use our hook to persist data in the localStorage. A link to the project repo can be found here and for our Qwik starter.dev showcase app, here. If you have any questions or run into any trouble, feel free to reach out on Twitter or on our Discord....

Chinedu Okpala5 mins
Qwik

How to Setup Storybook in a Qwik Project

Introduction Storybook is a great tool for testing and visualizing your components in different states. In this article, we will see how to setup Storybook in a Qwik project. Qwik Qwik is a new JavaScript framework by Misko Hevery, creator of Angular, for building frontend browser applications. The major benefit of Qwik is its performance optimization, and this is achieved through zero loading, resumability, lazy loading, reduced rendering, scalability, and code once. For more information on Qwik, you can check out the docs, github repo, and discord. Project Set Up To get started, we need to create a new Qwik app. We can do this by running the following command in our terminal: Initialize Storybook Storybook, unfortunately, doesn’t have a Qwik template yet because it is a new Framework. So the work around is to use the html template . Using the command below, we can initialize Storybook: During the initialization, Storybook would ask to automatically install some optional dependencies such as; EslintPlugin and npm7, you can accept or reject it. To accept, type y and hit Enter to progress. Storybook would try to setup a default stories folder, which is based on the template we choose, which is . It is not compatible with the Qwik compiler and will result in compilation errors, so we will have to delete it to avoid running into such errors. Project Structure Our project structure is already setup by Qwik, and Storybook initialization has created a .storybook folder. But we need to make some changes to the Storybook file extension since our project is in TypeScript. This is a snippet of the folders in our project: Configuring Storybook Since Qwik runs on Vite, we need to set up the viteFinal function in our main.ts file, which will give us the config that we will use to register our Qwik Vite plugin. Add this line of code in the configuration object: In the preview.ts file, this is where we configure how Storybook renders our stories. We need to execute Qwikloader. This will help in registering global browser events, and much more Qwik related benefits. We will replace the content in the file with the code below: This solution was found in this discussion: How to do component testing with Qwik?. I believe when the Qwik Storybook type template becomes available these configurations will be there by default. Now we are done with the configuration, let's run storybook and see what we have: Creating Stories We can create our first story, we will create a story for our Qwik app component. We will create this story for our default Qwik Header component. I modified the Header component to accept a menus props: Conclusion In this article, we saw how to setup Storybook in a Qwik project. We also saw how to create our first story. I hope you enjoyed this article. Thanks for reading. If you don't want to do these steps yourself, check out our starter.dev Qwik kit that already has Storybook enabled for your use here. A link to the project repo can be found here. If you have any questions or run into any trouble, feel free to reach out on Twitter....

Chinedu Okpala3 mins
Qwik

How to Build Apps with Great Startup Performance Using Qwik

In this article, we will recap the JS Drops Qwik workshop with Misko Hevery. This workshop provided an overview on Qwik, its unique features and a look at some example components. We will also address some of the questions raised at the end of the workshop. If you want to learn more about Qwik with Misko Hevery, then please check out this presentation on This Dot Media’s YouTube Channel. Also don’t forget to subscribe to get the latest on all things web development. Table of Contents What is Qwik? How to create a Counter Component in Qwik Unique features of Qwik Directory Based Routing Slots in Qwik Very little JavaScript in production Resumability with Qwik Lazy load components by default Questions asked during the workshop Are all these functions generated at build time or are they generated at runtime? What's the server consideration here (if any) or are we able to put everything behind a CDN? How do you access elements in Qwik? Can you force a download of something out of view? What is the choice to use $ on Qwik declarations? Can you explain the interop story with web components and Qwik? Any parts of the Qwik magic that aren’t available to us if, say, our web components are too complex? Is there an ideal use case for Qwik? When to use useWatch$ instead of useClientEffect$? Conclusion What is Qwik? Qwik is a web framework that builds fast web applications with consistent performance at scale regardless of size or complexity. To get started with Qwik, run the following command: The Qwik CLI will prompt options to scaffold a starter project on your local machine. To start the demo application, run npm start and navigate to http://127.0.0.1:5173/ in the browser. How to create a Counter Component in Qwik Create a sub directory in the routes directory named counter and add an index.tsx file with the component definition below. Now navigate to http://127.0.0.1:5173/counter and you should see the counter component rendered on the page. Unique features of Qwik Directory Based Routing Qwik is a directory based routing framework. When we initiated Qwik, it created a routes sub directory in the src directory and added index and layout files for route matching. The index.tsx is the base route component and the layout.tsx is the component for handling the base page layout. The sub directories in the route directory serve as the application’s structure for route matching with its index.tsx files as the route components. Every index.tsx file does a look up for the layout component. If it doesn’t exist in the same directory, then it moves up to the parent directory. Slots in Qwik Qwik uses slots as a way of connecting content from the parent component to the child projection. The parent component uses the q:slot attribute to identify the source of the projection and the element to identify the destination of the projection. To learn more about slots, please check out the Qwik documentation. Very little JavaScript in production In production, Qwik starts the application with no JavaScript at startup, which makes the startup performance really fast. To see this in action, open the browser’s dev tools, click on the Network tab, and on the Filter tab select JS. You will notice the Vite files for hot module reloading are currently the only JavaScript files served which will not be shipped to production. Go to the filter tab and check the invert checkbox then in the filter input type select Vite. Resumability with Qwik Qwik applications do not require hydration to resume an application on the client. To see this in action, click on the increment button and observe the browser’s dev tools network tab. You will notice Qwik is downloading only the required amount of JavaScript needed. The way Qwik attaches the event to the DOM and handles the state of components is that it serializes the attribute, which tells the browser where to download the event handler and its state. To learn more about serialization with Qwik, read through the Qwik documentation. By default, the code associated with the click event will not download until the user triggers that event. On this interaction, Qwik will only download the minimum code for the event handler to work. To learn more about Qwik events, please read through the [documentation] (https://qwik.builder.io/docs/components/events/#events)....

Hassan Sani5 mins
JavaScriptQwik