loading...

Testing Svelte components with Jest

jpblancodb profile image JPBlancoDB Updated on ・3 min read

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

Posted on by:

jpblancodb profile

JPBlancoDB

@jpblancodb

Developer, writing tech articles. Football fan and terrible defender. He/him.

Discussion

markdown guide
 

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

Painless migration! :)

 

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

 

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

error

 

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?