How to test anchor's href with react-testing-library
Asked Answered
S

10

141

I am trying to test my anchor tag. Once I click it, I want to see if the window.location.href is what I expect.

I've tried to render the anchor, click it, and then test window.location.href:

test('should navigate to ... when link is clicked', () => {
  const { getByText } = render(<a href="https://test.com">Click Me</a>);

  const link = getByText('Click Me');

  fireEvent.click(link);

  expect(window.location.href).toBe("https://www.test.com/");
});

I am expecting the test to pass, but instead the window.location.href is just "http://localhost/" meaning it is not getting updated for whatever reason. I even tried wrapping my expect with await wait, but that didn't work either. I can't find much information about testing anchors with react-testing-library. Maybe there is even a better way to test them than what I am doing. πŸ€·β€β™‚οΈ

Syncopation answered 6/9, 2019 at 18:40 Comment(0)
O
239

Jest uses jsdom to run its test. jsdom is simulating a browser, but it has some limitations. One of these limitations is the fact that you can't change the location. If you want to test that your link works, I suggest using getByRole with a name argument to check the href attribute of your <a>:

expect(screen.getByRole('link', { name: 'Click Me' })).toHaveAttribute('href', 'https://www.test.com/')

Historical Note

Previously this answer recommended the following, but because it relies on closest (whose use is discouraged by React Testing Library's emphasis on user-visible testing) it is considered a worse solution:

expect(screen.getByText('Click Me').closest('a')).toHaveAttribute('href', 'https://www.test.com/')
Orran answered 12/9, 2019 at 13:23 Comment(7)
I get TypeError: getByText(...).closest is not a function any ideas? – Keto
I guess that getByText finds nothing in your case – Orran
I just had to add screen: expect(screen.getByText('Click Me').closest('a')).toHaveAttribute('href', 'https://www.test.com/')and worked like charm. – Renault
I had the same problem and adding import "@testing-library/jest-dom/extend-expect"; to the top of the file fixed the problem – Marileemarilin
This is not as good as the answer from Dominic below, because of the search for closest('a'), it falls a bit outside of the spirit of react-testing-library ("The more your tests resemble the way your software is used, the more confidence they can give you") imo. getByRole('link') aligns closer with the spirit of the library since no such search is required. Even if the spirit of the library is itself questionable lol. Note, I have not downvoted - just saying... – Noleta
This did not work for me, I have this DOM: <a href="some-url">Next Step</a>, I tried expect(screen.getByText('Next Step').closest('a')).toHaveAttribute('href', "some-url"). But got this error: Expected the element to have attribute: href="some-url" Received: null – Sebastian
If you do not want to install "@testing-library/jest-dom/extend-expect" for one method call, then just use: screen.getByText('Click Me').getAttribute('href'); expect(href).toBe('https://www.test.com/'); – Oscillatory
S
95

I found a solution that may help others. The <a> element is considered a link role by React Testing Library. This should work:

expect(screen.getByRole('link')).toHaveAttribute('href', 'https://www.test.com');
Skylight answered 11/2, 2021 at 1:27 Comment(4)
this is probably the best answer as in the RTL documentation it says querying by role gets the highest priorities in regards to queries. About Queries – Hamrnand
This will work only if there is only 1 link on the page – Scrofulous
@Scrofulous If there are more links on the page, you need to use getAllByRole() and target the specific link you want. – Skylight
You can also .getByRole('link', { name: 'text of link' }). – Fugate
S
36

If you are using screen which should be the preferred way, by RTL authors:

const linkEl = screen.getByRole('link', { name: 'Click Me' });

expect(linkEl).toHaveAttribute('href', '...')

Similar, without screen (name can be string or RegExp):

const linkEl = getByRole('link', { name: /click me/i }); 
Scrofulous answered 3/6, 2021 at 21:29 Comment(0)
M
23

You can simply use this instead:

expect(getByText("Click Me").href).toBe("https://www.test.com/")
Minuteman answered 29/3, 2020 at 14:12 Comment(3)
That does not prevent from a development bug where there's a prop href but no <a />. Think in dynamic components, for example, a button that renders as a link. Of course, it wouldn't make sense but to force failure on your tests you need to validate that the property href is being used in a link element. – Bookstore
you need to reference to screen , but even doing so you will get a type error because: TS2339: Property 'href' does not exist on type 'HTMLElement' – Finalize
In react this translates /link into http://localhost/link which causes the test to fail. Using toHaveAttribute works correctly. – Delphinedelphinia
T
8

simple and easy.

try this

  it('should be a link that have href value to "/login"', () => {
    render(<SigningInLink />);
    const link = screen.getByRole('link', { name: /log in/i });
    expect(link.getAttribute('href')).toBe('/login');
  });
Townsley answered 30/8, 2021 at 13:18 Comment(0)
E
7

Maybe its overtly engineered in this case. But you can also use data-testid attribute. This guarantees that you are getting the a tag. I think there are benefit to this for more complex components.

test('should navigate to ... when link is clicked', () => {
  const { getByTestId } = render(<a data-testid='link' href="https://test.com">Click Me</a>);

  expect(getByTestId('link')).toHaveAttribute('href', 'https://test.com');
   
});
Ethel answered 22/3, 2021 at 3:51 Comment(0)
A
6

This is what I use:

const linkToTest = screen.getByRole("link", { name: /link to test/i })
// I used regex here as a value of name property which ignores casing

expect(linkToTest.getAttribute("href")).toMatchInlineSnapshot();

and then run the test, brackets of toMatchInlineSnapshot will be filled with the value that's there in your code.

This has the advantage of not hard coding it, and maintaining this will be easier.

For eg: it will be filled like this:

expect(linkToTest.getAttribute("href")).toMatchInlineSnapshot(`"link/to/somewhere"`);

and next time, suppose you change this link to something else in your codebase, the runner will ask you if you want to update, press u and it will be updated. (Note, that you need to check that this update is correct).

Know more about inline snapshots on Jest Docs

Abet answered 1/1, 2022 at 16:55 Comment(0)
S
5

You may have several links to check on a page. I found these suggestions very useful. What I did to adapt it to checking 5 links on the same page -

  1. query the link on the page with the screen() method best practice - it is more reliable
  2. assert the link is on the page
  3. assign the link to a variable
  4. call event on the link
  5. ensure the url toHaveAttribute method rather than navigating with the window object - In react with the virtual DOM, if you try and navigate to another page the link directs to http://localhost/link rather than testing the redirect
test('should navigate to url1 when link is clicked', () => {

const componentName = render(<Component/>)

const url1 = getByText("https://www.test.com/")
expect(ur1).toBeInTheDocument()
screen.userEvent.click(url1);
expect(url1).toHaveAttribute('href', 'https://www.test.com/')

});
Sulphathiazole answered 4/11, 2021 at 11:15 Comment(0)
G
3

This is what worked for me:


expect(screen.getByText('Click Me').closest('a')?.href).toEqual('https://test.com');

Gibbous answered 24/5, 2022 at 12:54 Comment(0)
W
0

Here is what I did, works for me using testing-library/react

import { render, screen } from '@testing-library/react';
import {HomeFeature} from '../Components/HomeFeature.tsx';

let imp_content = [
    {
        title: "Next.js",
        link: "https://nextjs.org/",
    },
    {
        title: "React",
        link: "https://reactjs.org/",
    },
    {
        title: "Firebase",
        link: "https://firebase.google.com/",
    },
    {
        title: "Tailwind",
        link: "https://tailwindcss.com/",
    },
    {
        title: "TypeScript",
        link: "https://www.typescriptlang.org/",
    },
    {
        title: "Jest.js",
        link: "https://jestjs.io/",
    },
    {
        title: "testing-library/react",
        link: "https://testing-library.com/",
    }
];

imp_content.map((content) => {
    test(`${content.title} contains ${content.link}`, () => {
        render(<HomeFeature />);
        expect(screen.getByText(content.title).closest('a')).toHaveAttribute('href', content.link);
    })
})
Worldweary answered 31/5, 2022 at 22:39 Comment(1)
Hello I would like to point out that your answer violates the eslint-plugin-testing-library/no-node-access rule due to using .closest to access nodes when the library already provides methods for querying DOM elements. – Dickenson

© 2022 - 2024 β€” McMap. All rights reserved.