Test Driven Development with Svelte - Querying Elements

basarbk

Basar Buyukkahraman

Posted on August 31, 2021

Test Driven Development with Svelte - Querying Elements

In the previous one we have installed the necessary dependencies for testing svelte app. Now in this one we are going to see how we can query the elements how we can interact with them.

Building the Registration Form

If the test is not running already, open console and run npm test
Lets start by creating a new svelte component and corresponding test module.
Let's add just the SignUpPage.svelte file. Leave it empty for now. And lets create SignUpPage.spec.js file.
You will see a fail message in console for SignUpPage.spec, because we don't have a test in it yet. Lets start with our first test

and since this file is just created lets start with the imports

// SignUpPage.spec.js
import SignUpPage from './SignUpPage.svelte';
import { render, screen } from '@testing-library/svelte';
import '@testing-library/jest-dom';
Enter fullscreen mode Exit fullscreen mode

now lets add our first test. In this page we will have form form sign up and lets make sure we have a header about it.

// SignUpPage.spec.js
it('has Sign Up header', () => {
    render(SignUpPage);
    const header = screen.getByRole('heading', { name: 'Sign Up'});
    expect(header).toBeInTheDocument();
})
Enter fullscreen mode Exit fullscreen mode

We are using two functions of testing-library. First we are rendering the component. The test runner, Jest is using jsdom. And it is providing us a browser-like environment. And the testing library is rendering the component into that document. And then we run queries coming with screen to get the elements we are looking for in that document.
To get a better view about all available query functions, you can check the testing-library documentation
from docs

Queries are the methods that Testing Library gives you to find elements on the page. There are several types of queries ("get", "find", "query"); the difference between them is whether the query will throw an error if no element is found or if it will return a Promise and retry.

So in this one we are using getByRole query. With this query, we are able to get the elements based on their accessibility roles.

We are looking for an element with heading role in this page. This means we will have h1-h6 elements or another html element having this role set for it.

After saving this test module, jest will run tests and in console you will see fail message.

TestingLibraryElementError: Unable to find an accessible
element with the role "heading" and name "Sign Up"
Enter fullscreen mode Exit fullscreen mode

Now we can fix it by adding header to our SignUpPage.svelte

<!-- SignUpPage.svelte -->
<h1>Sign Up</h1>
Enter fullscreen mode Exit fullscreen mode

After adding this one, test will pass.

Now lets add form inputs. User will be entering the sign up data and we need to have couple of inputs for it. Lets start with username input.
We will have multiple input elements and to tell which input is for which field, we need to show some text about it to user. We can use placeholder text for the inputs or we can associate labels with them. Testing-library is providing both query types. Lets go with placeholder first.

// SignUpPage.spec.js
it('has input for username', () => {
    render(SignUpPage);
    const input = screen.getByPlaceholderText('Username');
    expect(input).toBeInTheDocument();
})
Enter fullscreen mode Exit fullscreen mode

To get the inputs based on their placeholder, we user getByPlacholderText function.

Lets fix this by adding this input

<!-- SignUpPage.svelte -->
<h1>Sign Up</h1>
<!-- Right after this h1 lets add input and
lets set the placeholder for it -->
<input placeholder="Username" />
Enter fullscreen mode Exit fullscreen mode

This is fixing the test.
If you would like to use Label, instead of placeholder, then the test would be something like this.

// SignUpPage.spec.js
it('has input for username', () => {
    render(SignUpPage);
    // just replace getByPlaceholderText with getByLabelText
    const input = screen.getByLabelText('Username');
    expect(input).toBeInTheDocument();
})
Enter fullscreen mode Exit fullscreen mode

and the corresponding implementation would be like this

<!-- SignUpPage.svelte -->
<label for="username">Username</label>
<input id="username" />
Enter fullscreen mode Exit fullscreen mode

We add a label having Username text and associate with the input element.
After this change test will pass.

Just like this one add inputs for email and password. They are basically copy of the username input test

<!-- SignUpPage.spec.js -->
it('has input for email', () => {
    render(SignUpPage);
    const input = screen.getByLabelText('E-mail');
    expect(input).toBeInTheDocument();
})
it('has input for password', () => {
    render(SignUpPage);
    const input = screen.getByLabelText('Password');
    expect(input).toBeInTheDocument();
})
Enter fullscreen mode Exit fullscreen mode

and corresponding inputs are here

<!-- SignUpPage.svelte -->
<h1>Sign Up</h1>
<label for="username">Username</label>
<input id="username" />
<!-- -->
<label for="email">E-mail</label>
<input id="email" />
<label for="password">Password</label>
<input id="password" />
Enter fullscreen mode Exit fullscreen mode

After these changes the tests will be passing.

To see the progress on browser you can run the application like this.

npm run dev
Enter fullscreen mode Exit fullscreen mode

To see the SignUpPage, make sure to update main.js in the project

// main.js
import SignUpPage from './SignUpPage.svelte';

const app = new SignUpPage({
    target: document.body
});

export default app;
Enter fullscreen mode Exit fullscreen mode

In password inputs we must be masking the user entries as they type. But currently, if you type anything to password input, all will be in clear text. Lets fix that

// SignUpPage.spec.js
it('has type as password for password input', () => {
    render(SignUpPage);
    const input = screen.getByLabelText('Password');
    expect(input.type).toBe('password');
})
Enter fullscreen mode Exit fullscreen mode

Now we need to check an implementation detail for this case. We are looking for input's type attribute to have the value of password.

<!-- SignUpPage.svelte -->
<label for="password">Password</label>
<!-- just setting type for the input -->
<input id="password" type="password"/>
Enter fullscreen mode Exit fullscreen mode

Now the tests are passing. And if you check on browser, you will see the password input is masking the user entry.

Since the password is masked now, to make sure user enters the value they intend to, lets add second password input for confirmation.
Just copy and password last two password input tests and update their description and label texts.

// SignUpPage.spec.js
it('has input for password repeat', () => {
    render(SignUpPage);
    const input = screen.getByLabelText('Password Repeat');
    expect(input).toBeInTheDocument();
})
it('has type as password for password repeat input', () => {
    render(SignUpPage);
    const input = screen.getByLabelText('Password Repeat');
    expect(input.type).toBe('password');
})
Enter fullscreen mode Exit fullscreen mode

and repeating same step on svelte component too.

<!-- SignUpPage.svelte -->
<label for="password-repeat">Password Repeat</label>
<input id="password-repeat" type="password"/>
Enter fullscreen mode Exit fullscreen mode

We are about to complete this form. All we need is just a button to let user send the request. Adding a test for it

// SignUpPage.spec.js
it('has Sign Up button', () => {
    render(SignUpPage);
    const button = screen.getByRole('button', { name: 'Sign Up'});
    expect(button).toBeInTheDocument();
})
Enter fullscreen mode Exit fullscreen mode

and lets add button to our page.

<!-- SignUpPage.svelte -->
<button>Sign Up</button>
Enter fullscreen mode Exit fullscreen mode

Lets add one final test for this part. Lets make sure the button is initially disabled. Later we are going to enable it after user enters same value to both password and password repeat inputs.

it('disables the button initially', () => {
    render(SignUpPage);
    const button = screen.getByRole('button', { name: 'Sign Up'});
    // for the matcher functions we are using
    // '@testing-library/jest-dom' and for this case
    // we can use
    expect(button).toBeDisabled();

    // or we have option to use matcher with 'not' function
    // expect(button).not.toBeEnabled();
})
Enter fullscreen mode Exit fullscreen mode

and we fix it by adding disabled attribute to button

<button disabled>Sign Up</button>
Enter fullscreen mode Exit fullscreen mode

All tests are passing now. We have our form now.

Next, we will implement the interactions..

Resources

Github repo for this project can be found here

GitHub logo basarbk / dev-svelte-tdd

Repository of article project about test driven development with svelte published in dev.to

You can also check this video tutorial for the same form creation for svelte projects

And if you would be interested, I have a full course TDD in svelte. Svelte with Test Driven Development

Thanks for reading

💖 💪 🙅 🚩
basarbk
Basar Buyukkahraman

Posted on August 31, 2021

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

Sign up to receive the latest update from our blog.

Related