DEV Community

JPBlancoDB
JPBlancoDB

Posted on • Edited on

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

Top comments (4)

Collapse
 
asm0dey profile image
Paul

How do you test {#await} blocks with jest?

Collapse
 
spirift profile image
Michael

Good explanation of how to get a basic testing setup done. Have you had much luck with testing modules and mocking svelte components, and/or with testing components that use 3rd party components?

Collapse
 
ruttmann profile image
Marlon Erich Ruttmann

Really nice that the testing process is the same as in React.

Painless migration! :)

Collapse
 
wchr profile image
Wachira • Edited

I am having trouble deploying to now, I am getting this error

error