The Svelte framework is, at the moment of this writing, one of the most popular Javascript frameworks. Last year, it overtook Vue.js in popularity according to the State of JS 2019. Unlike other frameworks, Svelte does not do its DOM-updating work in the browser using the Virtual DOM, but instead compiles Javascript code in its build step that efficiently updates your DOM when a state change occurs. This strategy has led to its fast-rising popularity and adoption. In this tutorial, we will create an automated continuous integration (CI) pipeline that automates the process of running tests written for Svelte apps.

Prerequisites

To follow this tutorial, a few things are required:

  1. Basic knowledge of Javascript
  2. Node.js installed on your system
  3. A CircleCI account
  4. A GitHub account

With all these installed and set up, let’s begin the tutorial.

Scaffolding a new Svelte project

To begin, we need to create our Svelte project. In an appropriate location on your system, run the following command:

npx degit sveltejs/template svelte-testing

This command will immediately begin scaffolding a new Svelte project. The svelte-testing part defines the project folder’s name. You can use any preferred name.

Once the scaffolding process is done, go into the root of your project and install the required packages:

cd svelte-testing
npm install

Once the installation is complete, run the following command to boot up a server to serve the application in development mode:

npm run dev

This will serve the application at http://localhost:5000/ (5000 is the default port. When it is in use, another port will be assigned instead).

App first view

We now have a functional Svelte app up and running.

Setting up testing with Jest and the Svelte testing library

In order to write and run tests for our Svelte project, we need to set up Jest and the Svelte testing library. To achieve this, we need to install a bunch of packages in our project. The following libraries are required for this setup:

  • jest: To use Jest as our test runner
  • @babel/core, @babel/preset-env, babel-jest: To enable the use of ES6 JavaScript in our test files
  • @testing-library/svelte: The Svelte testing library
  • svelte-jester: This package helps you compile the Svelte components before using them in Jest
  • @testing-library/jest-dom: Useful in adding handy assertions to Jest

All of these packages need to be installed as development dependencies. Install them all at once with the following command:

npm install --save-dev jest svelte-jester @testing-library/jest-dom @testing-library/svelte @babel/core @babel/preset-env babel-jest

Once all of these install, the next step is to add configurations to our project to specify how these packages operate.

Add the following jest configuration section to your package.json file. (Refer to our package.json file here.

"jest": {
    "transform": {
        "^.+\\.svelte$": "svelte-jester",
        "^.+\\.js$": "babel-jest"
    },
    "moduleFileExtensions": [
        "js",
        "svelte"
    ],
    "setupFilesAfterEnv": [
        "@testing-library/jest-dom/extend-expect"
    ]
}

Next, at the root of your project, create a Babel configuration file named .babelrc and place the following configuration in it:

{
  "presets": [["@babel/preset-env", { "targets": { "node": "current" } }]]
}

Finally, add a test script to the scripts section of the package.json file:

...

"scripts": {
    ...
    "test": "jest src"
}

Now, you have everything needed to run tests for your Svelte components with Jest.

Adding tests to our Svelte project

To begin writing tests, first create a simple component that will be tested later on. Inside the src/components folder, create a new file named ButtonComp.svelte and input the following code:

<script>
  export let name

  let buttonText = 'Button'

  function handleClick() {
    buttonText = 'Button Clicked'
  }
</script>

<h1>Hello {name}!</h1>

<button on:click="{handleClick}">{buttonText}</button>

In the code above, we create a component that displays a header containing a string and a dynamic name variable, which can be passed into the component by a parent component. We also display a button that changes its label when clicked by having its click event handled by a handleClick method.

Let’s make use of this component in our application by placing it in our application’s homepage. Open the src/App.svelte file and replace the entire contents with the code snippet below:


<script>
  export let name;

  import ButtonComp from "./components/ButtonComp.svelte";
</script>

<main>
  <h1>Hello {name}!</h1>
  <p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>

  <ButtonComp name="Svelte" />
</main>

<style>
  main {
    text-align: center;
    padding: 1em;
    max-width: 240px;
    margin: 0 auto;
  }

  h1 {
    color: #ff3e00;
    text-transform: uppercase;
    font-size: 4em;
    font-weight: 100;
  }

  @media (min-width: 640px) {
    main {
      max-width: none;
    }
  }
</style>

In the above code, we import our ButtonComp component, and create an instance of it within our home page, passing in the Svelte value to the name prop.

Let’s see how our homepage looks with these updates.

App with component

Our header displays Hello Svelte because we passed the string Svelte to the name prop. Click the button and observe the label change from Button to Button Clicked. We are going to be writing tests to assert these behaviours exhibited by our component.

Inside the src folder, create a new __tests__ folder (double underscores on both sides). This is the standard folder for Jest to search for test suites/scripts.

Within __tests__, let’s write our first test suite by creating a ButtonCompTest.js file and adding the following code:

import "@testing-library/jest-dom/extend-expect";

import { render, fireEvent } from "@testing-library/svelte";

import ButtonComp from "../components/ButtonComp";

test("'Hello Svelte' is rendered on the header", () => {
  const { getByText } = render(ButtonComp, { name: "Svelte" });

  expect(getByText("Hello Svelte!")).toBeInTheDocument();
});

test("Button text changes when button is clicked", async () => {
  const { getByText } = render(ButtonComp, { name: "Svelte" });
  const button = getByText("Button");

  await fireEvent.click(button);

  expect(button).toHaveTextContent("Button Clicked");
});

In the above file, we import the ButtonComp component and test it. The first test confirms that the label Hello Svelte! is found in our component after rendering it by passing the string Svelte as the value of the name prop.

The second test checks the behaviour of the button within our component. We render it once again and get a reference to the button within it. Then we fire the click event on the button, and check its current state to confirm that the button’s label indeed changes to the expected Button Clicked string.

Let’s save the file and use the test command on the terminal to run our tests:

npm run test

Local test run

We now have our tests running successfully and they both passed.

Creating a CircleCI project

Our next task is to get our Svelte project set up on CircleCI. Begin by pushing your project to GitHub.

Then go to the Add Projects page on the CircleCI dashboard to add the project.

Add Project - CircleCI

Click Set Up Project to begin.

Add Config - CircleCI

Next, click Add Manually. You get a prompt to either download a configuration file for the pipeline or start building.

Build Prompt - CircleCI

Click Start Building. This build will fail because we have not set up our configuration file yet. We’ll do that later on.

Automating our tests

The next step in this tutorial is to write our CI pipeline script to automate the testing process.

Inside the root of the project, create a new folder named .circle. Within this folder, create a config.yml file and add the following code into it:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: circleci/node:10.16.3
    steps:
      - checkout
      - run:
          name: Update NPM
          command: "sudo npm install -g npm@5"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: Install Dependencies
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: Run tests
          command: npm run test

In the file above, we first pull in a Node.js Docker image. Then we update npm to ensure that we have the most current version running. Next, we install our dependencies and cache them. With all dependencies in place, we run our tests.

Commit and push these changes to your GitHub repository. This will trigger the pipeline into action and run. This can be viewed on the Pipelines page of the CircleCI console.

Build Success - CircleCI

Let’s view the detailed process by clicking build to see the test results.

Build Success - CircleCI

Our tests are running perfectly!

Let’s continuously integrate more tests by creating another test suite AppTest.js inside the __tests__ folder and place the following code inside (src/__tests__/AppTest.js):

import "@testing-library/jest-dom/extend-expect";

import { render } from "@testing-library/svelte";

import App from "../App";

test("'Hello World' is rendered on the header", () => {
  const { getByText } = render(App, { name: "World" });

  expect(getByText("Hello World!")).toBeInTheDocument();
});

Now, commit your changes and once again push them to the GitHub repository to run the pipeline.

Build Success - CircleCI

Conclusion

The full project can be seen here on GitHub.

In this tutorial, we built a continuous integration (CI) pipeline to automate the testing of our Svelte app. As we continue to add more features and more tests to the project, a simple push to the repository is all that is needed to ensure that our tests are run. Getting pass or fail results helps guide the development and keeps us from committing bad code to our repo.

Happy coding!


Fikayo Adepoju is a LinkedIn Learning (Lynda.com) Author, Full-stack developer, technical writer, and tech content creator proficient in Web and Mobile technologies and DevOps with over 10 years experience developing scalable distributed applications. With over 40 articles written for CircleCI, Twilio, Auth0, and The New Stack blogs, and also on his personal Medium page, he loves to share his knowledge to as many developers as would benefit from it. You can also check out his video courses on Udemy.

Read more posts by Fikayo Adepoju