Testing Svelte components with Jest

jpblancodb

JPBlancoDB

Posted on November 26, 2019

Testing Svelte components with Jest

I haven't found much information about how to test svelte components, so continuing with my previous article about creating a blog with Svelte, I will now explain how to test it. We are going to use Jest, Testing Library and jest-dom

Let's start by installing the required dependencies:

npm i @babel/core @babel/preset-env jest babel-jest -D
npm i jest-transform-svelte @testing-library/svelte @testing-library/jest-dom -D

Now, we need to create a jest.config.js and babel.config.js at the root folder of our project (more about jest configuration: Jest Configuration)

//jest.config.js
module.exports = {
  transform: {
    "^.+\\.svelte$": "jest-transform-svelte",
    "^.+\\.js$": "babel-jest"
  },
  moduleFileExtensions: ["js", "svelte"],
  testPathIgnorePatterns: ["node_modules"],
  bail: false,
  verbose: true,
  transformIgnorePatterns: ["node_modules"],
  setupFilesAfterEnv: ["@testing-library/jest-dom/extend-expect"]
};
//babel.config.js
module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: {
          node: "current"
        }
      }
    ]
  ]
};

Finally, we should add into the scripts section of package.json the following:

"test": "jest src",
"test:watch": "npm run test -- --watch"

Result:

//package.json
"scripts": {
    "dev": "sapper dev",
    "build": "sapper build",
    "export": "sapper export --legacy",
    "start": "node __sapper__/build",
    "test": "jest src",
    "test:watch": "npm run test -- --watch"
},

Done! We can start writing our tests, let's create our first one 💪! You could create all your tests together within the same folder __tests__ but I rather prefer to have my tests in the same folder of the component, so I will create an index.spec.js in src/routes/ folder:

//index.spec.js
import { render } from "@testing-library/svelte";
import Index from "./index.svelte";

describe("index component", () => {
  test("should render component correctly", () => {
    const { container } = render(Index);

    expect(container).toContainHTML("<div></div>");
  });
});

Awesome 😎! We have our first test! But, what happened? Yes, it is failing with TypeError: Cannot read property 'length' of undefined, because is not triggering the preload, so our articles variable is not defined. What we could do is pass an empty array of articles as props.

test("should render component correctly", () => {
  const { container } = render(Index, {
    props: {
      articles: []
    }
  });

  expect(container).toContainHTML("<div></div>");
});

Great! Now is passing. But we are not really testing our component, so what we could do now is actually pass an article, so let's create a new test:

//index.spec.js
test("should render articles", () => {
  const title = "My title";
  const description = "some description";
  const readable_publish_date = "10 Oct";
  const canonical_url = "url";
  const { container, getByText } = render(Index, {
    props: {
      articles: [
        {
          title,
          canonical_url,
          readable_publish_date,
          description
        }
      ]
    }
  });

  expect(container.querySelector("a").href).toBe(
    `http://localhost/${canonical_url}`
  );
  expect(getByText(title)).toBeInTheDocument();
  expect(getByText(readable_publish_date)).toBeInTheDocument();
  expect(getByText(description)).toBeInTheDocument();
});

Again! The same error but now because of the tags! Should we validate that tags is not undefined before doing the each? Or this is not possible? In my opinion, I think validating this is not necessary as the api returns an empty array of tags in case is empty, so we should only fix our test by adding an empty array of tags.

//index.spec.js
test("should render articles", () => {
  const title = "My title";
  const description = "some description";
  const readable_publish_date = "10 Oct";
  const canonical_url = "url";
  const { container, getByText } = render(Index, {
    props: {
      articles: [
        {
          title,
          canonical_url,
          readable_publish_date,
          description,
          tag_list: []
        }
      ]
    }
  });

  expect(container.querySelector("a").href).toBe(
    `http://localhost/${canonical_url}`
  );
  expect(getByText(title)).toBeInTheDocument();
  expect(getByText(readable_publish_date)).toBeInTheDocument();
  expect(getByText(description)).toBeInTheDocument();
});

Finally, we could test that the tags render properly:

//index.spec.js
test("should render articles with tags", () => {
  const { getByText } = render(Index, {
    props: {
      articles: [
        {
          tag_list: ["my-tag"]
        }
      ]
    }
  });

  expect(getByText("#my-tag")).toBeInTheDocument();
});

Done! Now that we have our tests, you could refactor our component into smaller pieces, for example, you could extract a card component or also a Tags.svelte, try it! Let me know how it went in a comment! I would like to see the end results of your own blog app!

If you have any question or suggestion, leave a comment or contact me via Twitter

💖 💪 🙅 🚩
jpblancodb
JPBlancoDB

Posted on November 26, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related