diff --git a/tests/components/layout.test.tsx b/tests/components/layout.test.tsx
new file mode 100644
index 00000000..58672480
--- /dev/null
+++ b/tests/components/layout.test.tsx
@@ -0,0 +1,84 @@
+import * as React from "react";
+import * as Gatsby from "gatsby";
+
+import { render } from "@testing-library/react";
+import { Layout } from "../../src/components";
+
+const link = jest.spyOn(Gatsby, `Link`);
+const useStaticQuery = jest.spyOn(Gatsby, `useStaticQuery`);
+const mockUseStaticQuery = {
+ site: {
+ siteMetadata: {
+ title: "My Site",
+ },
+ },
+};
+
+describe("Layout component", () => {
+ beforeEach(() => {
+ useStaticQuery.mockImplementation(() => mockUseStaticQuery);
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-expect-error
+ link.mockImplementation(({ to, children }) => {children});
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it("renders the site title", () => {
+ const { getByText } = render(
+
+ Content
+
+ );
+ expect(getByText("My Site")).toBeInTheDocument();
+ });
+
+ it("renders the page title", () => {
+ const { getByText } = render(
+
+ Content
+
+ );
+ expect(getByText("Test page")).toBeInTheDocument();
+ });
+
+ it("renders the content", () => {
+ const { getByText } = render(
+
+ Content
+
+ );
+ expect(getByText("Content")).toBeInTheDocument();
+ });
+
+ it("renders the blog link", () => {
+ const { getByText } = render(
+
+ Content
+
+ );
+ expect(getByText("Blog")).toBeInTheDocument();
+ });
+
+ const navLinks = {
+ Blog: "/",
+ Tags: "/tags",
+ About: "/about",
+ };
+
+ for (const [name, path] of Object.entries(navLinks)) {
+ it(`links the ${name} link to ${path}`, () => {
+ const { getByRole } = render(
+
+ Content
+
+ );
+
+ const link = getByRole("link", { name });
+
+ expect(link.getAttribute("href")).toBe(path);
+ });
+ }
+});