From 29f222049297f063ed3fb15813d0bc40d9510c70 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Fri, 21 Mar 2025 09:46:18 +0000 Subject: [PATCH 1/4] Add additional reaper tests --- .../testcontainers/src/reaper/reaper.test.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/packages/testcontainers/src/reaper/reaper.test.ts b/packages/testcontainers/src/reaper/reaper.test.ts index 4421c4c56..e3b8aad94 100644 --- a/packages/testcontainers/src/reaper/reaper.test.ts +++ b/packages/testcontainers/src/reaper/reaper.test.ts @@ -1,4 +1,5 @@ import { ContainerRuntimeClient, getContainerRuntimeClient } from "../container-runtime"; +import { RandomUniquePortGenerator } from "../utils/port-generator"; describe("Reaper", { timeout: 120_000 }, () => { let client: ContainerRuntimeClient; @@ -12,6 +13,42 @@ describe("Reaper", { timeout: 120_000 }, () => { client = await getContainerRuntimeClient(); }); + it("should create disabled reaper when TESTCONTAINERS_RYUK_DISABLED=true", async () => { + vitest.stubEnv("TESTCONTAINERS_RYUK_DISABLED", "true"); + + vi.spyOn(client.container, "list").mockResolvedValue([]); + const reaper = await getReaper(); + + // DisabledReaper has an empty containerId + expect(reaper.containerId).toBe(""); + + // Should not throw exceptions when methods are called + expect(() => reaper.addSession("test-session")).not.toThrow(); + expect(() => reaper.addComposeProject("test-project")).not.toThrow(); + }); + + it("should use custom port when TESTCONTAINERS_RYUK_PORT is set", async () => { + const customPort = (await new RandomUniquePortGenerator().generatePort()).toString(); + vitest.stubEnv("TESTCONTAINERS_RYUK_PORT", customPort); + + vi.spyOn(client.container, "list").mockResolvedValue([]); + const reaper = await getReaper(); + + const reaperContainer = client.container.getById(reaper.containerId); + const ports = (await reaperContainer.inspect()).HostConfig.PortBindings; + expect(ports["8080"][0].HostPort).toBe(customPort); + }); + + it("should reuse existing reaper container if one is already running", async () => { + const reaper = await getReaper(); + const reaperContainerInfo = (await client.container.list()).filter((c) => c.Id === reaper.containerId)[0]; + vi.spyOn(client.container, "list").mockResolvedValue([reaperContainerInfo]); + + const reaper2 = await getReaper(); + + expect(reaper2.containerId).toBe(reaper.containerId); + }); + it("should create Reaper container without RYUK_VERBOSE env var by default", async () => { vi.spyOn(client.container, "list").mockResolvedValue([]); const reaper = await getReaper(); From 0fb5e9aee2639da5de2bbf425ff18c17493235f7 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Fri, 21 Mar 2025 09:50:38 +0000 Subject: [PATCH 2/4] Reactor setting env vars in tests --- .../auth/get-auth-config.test.ts | 21 ++++++----- .../src/container-runtime/image-name.test.ts | 37 ++++++------------- .../generic-container-reuse.test.ts | 6 +-- .../testcontainers/src/reaper/reaper.test.ts | 8 ++-- 4 files changed, 30 insertions(+), 42 deletions(-) diff --git a/packages/testcontainers/src/container-runtime/auth/get-auth-config.test.ts b/packages/testcontainers/src/container-runtime/auth/get-auth-config.test.ts index 3ea88ffe1..fd45721ce 100644 --- a/packages/testcontainers/src/container-runtime/auth/get-auth-config.test.ts +++ b/packages/testcontainers/src/container-runtime/auth/get-auth-config.test.ts @@ -14,20 +14,23 @@ describe("get auth config", () => { }); afterEach(() => { - delete process.env.DOCKER_AUTH_CONFIG; + vi.unstubAllEnvs(); vi.resetModules(); }); it("should use DOCKER_AUTH_CONFIG environment variable as Docker config", async () => { - process.env.DOCKER_AUTH_CONFIG = JSON.stringify({ - auths: { - "https://registry.example.com": { - email: "user@example.com", - username: "user", - password: "pass", + vi.stubEnv( + "DOCKER_AUTH_CONFIG", + JSON.stringify({ + auths: { + "https://registry.example.com": { + email: "user@example.com", + username: "user", + password: "pass", + }, }, - }, - }); + }) + ); const { getAuthConfig } = await import("./get-auth-config"); expect(await getAuthConfig("https://registry.example.com")).toEqual({ username: "user", diff --git a/packages/testcontainers/src/container-runtime/image-name.test.ts b/packages/testcontainers/src/container-runtime/image-name.test.ts index fe919ae62..d6f0a66ee 100644 --- a/packages/testcontainers/src/container-runtime/image-name.test.ts +++ b/packages/testcontainers/src/container-runtime/image-name.test.ts @@ -10,6 +10,10 @@ describe("ContainerImage", () => { expect(imageName.equals(new ImageName("anotherRegistry", "image", "tag"))).toBe(false); }); + afterEach(() => { + vi.unstubAllEnvs(); + }); + describe("string", () => { it("should work with registry", () => { const imageName = new ImageName("registry", "image", "tag"); @@ -57,21 +61,12 @@ describe("ContainerImage", () => { it.each(["custom.com/registry", "custom.com/registry/"])( "should substitute no registry with the one provided via TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX when provided registry is %s", (customRegistry: string) => { - const oldEnvValue = process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX; - try { - process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX = customRegistry; - const imageName = new ImageName(undefined, "image", "tag"); - expect(imageName.registry).toBe("custom.com"); - expect(imageName.image).toBe("registry/image"); - expect(imageName.tag).toBe("tag"); - expect(imageName.string).toBe("custom.com/registry/image:tag"); - } finally { - if (oldEnvValue === undefined) { - delete process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX; - } else { - process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX = oldEnvValue; - } - } + vi.stubEnv("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX", customRegistry); + const imageName = new ImageName(undefined, "image", "tag"); + expect(imageName.registry).toBe("custom.com"); + expect(imageName.image).toBe("registry/image"); + expect(imageName.tag).toBe("tag"); + expect(imageName.string).toBe("custom.com/registry/image:tag"); } ); }); @@ -176,18 +171,8 @@ describe("ContainerImage", () => { ])( "fromString with TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX set to $customRegistry", ({ customRegistry, expectedRegistry, expectedImagePrefix }) => { - let oldEnvValue: string | undefined; beforeEach(() => { - oldEnvValue = process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX; - process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX = customRegistry; - }); - - afterEach(() => { - if (oldEnvValue === undefined) { - delete process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX; - } else { - process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX = oldEnvValue; - } + vi.stubEnv("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX", customRegistry); }); it("should work", () => { diff --git a/packages/testcontainers/src/generic-container/generic-container-reuse.test.ts b/packages/testcontainers/src/generic-container/generic-container-reuse.test.ts index 6d53b7cbc..4bedca2ba 100644 --- a/packages/testcontainers/src/generic-container/generic-container-reuse.test.ts +++ b/packages/testcontainers/src/generic-container/generic-container-reuse.test.ts @@ -4,7 +4,7 @@ import { GenericContainer } from "./generic-container"; describe("GenericContainer reuse", { timeout: 180_000 }, () => { afterEach(() => { - process.env.TESTCONTAINERS_REUSE_ENABLE = undefined; + vi.unstubAllEnvs(); }); it("should not reuse the container by default", async () => { @@ -64,7 +64,7 @@ describe("GenericContainer reuse", { timeout: 180_000 }, () => { }); it("should not reuse the container when TESTCONTAINERS_REUSE_ENABLE is set to false", async () => { - process.env.TESTCONTAINERS_REUSE_ENABLE = "false"; + vi.stubEnv("TESTCONTAINERS_REUSE_ENABLE", "false"); const name = `there_can_only_be_one_${randomUuid()}`; const container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") @@ -90,7 +90,7 @@ describe("GenericContainer reuse", { timeout: 180_000 }, () => { it.each(["true", undefined])( "should reuse the container when TESTCONTAINERS_REUSE_ENABLE is set to %s", async (reuseEnable: string | undefined) => { - process.env.TESTCONTAINERS_REUSE_ENABLE = reuseEnable; + vi.stubEnv("TESTCONTAINERS_REUSE_ENABLE", reuseEnable); const name = `there_can_only_be_one_${randomUuid()}`; const container1 = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") diff --git a/packages/testcontainers/src/reaper/reaper.test.ts b/packages/testcontainers/src/reaper/reaper.test.ts index e3b8aad94..998116f71 100644 --- a/packages/testcontainers/src/reaper/reaper.test.ts +++ b/packages/testcontainers/src/reaper/reaper.test.ts @@ -8,13 +8,13 @@ describe("Reaper", { timeout: 120_000 }, () => { beforeEach(async () => { vi.resetModules(); - vitest.unstubAllEnvs(); + vi.unstubAllEnvs(); client = await getContainerRuntimeClient(); }); it("should create disabled reaper when TESTCONTAINERS_RYUK_DISABLED=true", async () => { - vitest.stubEnv("TESTCONTAINERS_RYUK_DISABLED", "true"); + vi.stubEnv("TESTCONTAINERS_RYUK_DISABLED", "true"); vi.spyOn(client.container, "list").mockResolvedValue([]); const reaper = await getReaper(); @@ -29,7 +29,7 @@ describe("Reaper", { timeout: 120_000 }, () => { it("should use custom port when TESTCONTAINERS_RYUK_PORT is set", async () => { const customPort = (await new RandomUniquePortGenerator().generatePort()).toString(); - vitest.stubEnv("TESTCONTAINERS_RYUK_PORT", customPort); + vi.stubEnv("TESTCONTAINERS_RYUK_PORT", customPort); vi.spyOn(client.container, "list").mockResolvedValue([]); const reaper = await getReaper(); @@ -60,7 +60,7 @@ describe("Reaper", { timeout: 120_000 }, () => { }); it("should propagate TESTCONTAINERS_RYUK_VERBOSE into Reaper container", async () => { - vitest.stubEnv("TESTCONTAINERS_RYUK_VERBOSE", "true"); + vi.stubEnv("TESTCONTAINERS_RYUK_VERBOSE", "true"); vi.spyOn(client.container, "list").mockResolvedValue([]); const reaper = await getReaper(); From 72a0e92560135bba174c413950aaf627f8123a98 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Fri, 21 Mar 2025 10:01:54 +0000 Subject: [PATCH 3/4] Always unstub envs --- .../src/container-runtime/auth/get-auth-config.test.ts | 1 - .../testcontainers/src/container-runtime/image-name.test.ts | 4 ---- .../src/generic-container/generic-container-reuse.test.ts | 4 ---- packages/testcontainers/src/reaper/reaper.test.ts | 2 -- vitest.config.ts | 1 + 5 files changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/testcontainers/src/container-runtime/auth/get-auth-config.test.ts b/packages/testcontainers/src/container-runtime/auth/get-auth-config.test.ts index fd45721ce..bb9e8887d 100644 --- a/packages/testcontainers/src/container-runtime/auth/get-auth-config.test.ts +++ b/packages/testcontainers/src/container-runtime/auth/get-auth-config.test.ts @@ -14,7 +14,6 @@ describe("get auth config", () => { }); afterEach(() => { - vi.unstubAllEnvs(); vi.resetModules(); }); diff --git a/packages/testcontainers/src/container-runtime/image-name.test.ts b/packages/testcontainers/src/container-runtime/image-name.test.ts index d6f0a66ee..4f87519cc 100644 --- a/packages/testcontainers/src/container-runtime/image-name.test.ts +++ b/packages/testcontainers/src/container-runtime/image-name.test.ts @@ -10,10 +10,6 @@ describe("ContainerImage", () => { expect(imageName.equals(new ImageName("anotherRegistry", "image", "tag"))).toBe(false); }); - afterEach(() => { - vi.unstubAllEnvs(); - }); - describe("string", () => { it("should work with registry", () => { const imageName = new ImageName("registry", "image", "tag"); diff --git a/packages/testcontainers/src/generic-container/generic-container-reuse.test.ts b/packages/testcontainers/src/generic-container/generic-container-reuse.test.ts index 4bedca2ba..785d92733 100644 --- a/packages/testcontainers/src/generic-container/generic-container-reuse.test.ts +++ b/packages/testcontainers/src/generic-container/generic-container-reuse.test.ts @@ -3,10 +3,6 @@ import { checkContainerIsHealthy } from "../utils/test-helper"; import { GenericContainer } from "./generic-container"; describe("GenericContainer reuse", { timeout: 180_000 }, () => { - afterEach(() => { - vi.unstubAllEnvs(); - }); - it("should not reuse the container by default", async () => { const name = `there_can_only_be_one_${randomUuid()}`; const container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") diff --git a/packages/testcontainers/src/reaper/reaper.test.ts b/packages/testcontainers/src/reaper/reaper.test.ts index 998116f71..593b06610 100644 --- a/packages/testcontainers/src/reaper/reaper.test.ts +++ b/packages/testcontainers/src/reaper/reaper.test.ts @@ -8,8 +8,6 @@ describe("Reaper", { timeout: 120_000 }, () => { beforeEach(async () => { vi.resetModules(); - vi.unstubAllEnvs(); - client = await getContainerRuntimeClient(); }); diff --git a/vitest.config.ts b/vitest.config.ts index 0c166ea7b..fb3d8d26d 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -7,6 +7,7 @@ export default defineConfig({ silent: "passed-only", mockReset: true, restoreMocks: true, + unstubEnvs: true, alias: { testcontainers: path.resolve(__dirname, "packages/testcontainers/src"), }, From b6f9337da7f0abee6e725797079ae73bfc91228e Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Fri, 21 Mar 2025 11:14:09 +0000 Subject: [PATCH 4/4] Remove additional reaper tests --- .../testcontainers/src/reaper/reaper.test.ts | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/packages/testcontainers/src/reaper/reaper.test.ts b/packages/testcontainers/src/reaper/reaper.test.ts index 593b06610..e6e06b47a 100644 --- a/packages/testcontainers/src/reaper/reaper.test.ts +++ b/packages/testcontainers/src/reaper/reaper.test.ts @@ -1,5 +1,4 @@ import { ContainerRuntimeClient, getContainerRuntimeClient } from "../container-runtime"; -import { RandomUniquePortGenerator } from "../utils/port-generator"; describe("Reaper", { timeout: 120_000 }, () => { let client: ContainerRuntimeClient; @@ -11,42 +10,6 @@ describe("Reaper", { timeout: 120_000 }, () => { client = await getContainerRuntimeClient(); }); - it("should create disabled reaper when TESTCONTAINERS_RYUK_DISABLED=true", async () => { - vi.stubEnv("TESTCONTAINERS_RYUK_DISABLED", "true"); - - vi.spyOn(client.container, "list").mockResolvedValue([]); - const reaper = await getReaper(); - - // DisabledReaper has an empty containerId - expect(reaper.containerId).toBe(""); - - // Should not throw exceptions when methods are called - expect(() => reaper.addSession("test-session")).not.toThrow(); - expect(() => reaper.addComposeProject("test-project")).not.toThrow(); - }); - - it("should use custom port when TESTCONTAINERS_RYUK_PORT is set", async () => { - const customPort = (await new RandomUniquePortGenerator().generatePort()).toString(); - vi.stubEnv("TESTCONTAINERS_RYUK_PORT", customPort); - - vi.spyOn(client.container, "list").mockResolvedValue([]); - const reaper = await getReaper(); - - const reaperContainer = client.container.getById(reaper.containerId); - const ports = (await reaperContainer.inspect()).HostConfig.PortBindings; - expect(ports["8080"][0].HostPort).toBe(customPort); - }); - - it("should reuse existing reaper container if one is already running", async () => { - const reaper = await getReaper(); - const reaperContainerInfo = (await client.container.list()).filter((c) => c.Id === reaper.containerId)[0]; - vi.spyOn(client.container, "list").mockResolvedValue([reaperContainerInfo]); - - const reaper2 = await getReaper(); - - expect(reaper2.containerId).toBe(reaper.containerId); - }); - it("should create Reaper container without RYUK_VERBOSE env var by default", async () => { vi.spyOn(client.container, "list").mockResolvedValue([]); const reaper = await getReaper();