GcpCompute is built to be tested without GCP, credentials, or the network. Two seams make that possible:

  1. req_options: [adapter: fun]Req's native adapter option lets you answer requests from an in-memory routing table instead of the wire.
  2. GcpCompute.TokenProvider.StaticGcpCompute.Config.local/1 defaults to it, so no Goth and no real token are required.

A minimal stubbed config

config =
  GcpCompute.Config.local!(
    project: "test-project",
    zone: "us-central1-a",
    req_options: [
      retry: false,
      adapter: fn request ->
        response =
          case {request.method, request.url.path} do
            {:get, "/compute/v1/projects/test-project/zones/us-central1-a/instances/w"} ->
              %Req.Response{status: 200, body: %{"name" => "w", "status" => "RUNNING"}}

            _ ->
              %Req.Response{status: 404, body: %{"error" => %{"code" => 404}}}
          end

        {request, response}
      end
    ]
  )

{:ok, instance} = GcpCompute.get_instance(config, "w")

The adapter returns {request, %Req.Response{}} — and the response body is an already-decoded map (no JSON round-trip needed).

A reusable routing-table helper

The library's own test suite uses a small helper, test/support/req_stub.ex, that turns a %{ {method, path} => response } map into a config:

config =
  GcpCompute.ReqStub.config(%{
    {:post, "/compute/v1/projects/test-project/zones/us-central1-a/instances"} =>
      {200, GcpCompute.ReqStub.operation("op-1", "PENDING")},
    {:post, "/compute/v1/projects/test-project/zones/us-central1-a/operations/op-1/wait"} =>
      {200, GcpCompute.ReqStub.operation("op-1", "DONE")},
    {:get, "/compute/v1/projects/test-project/zones/us-central1-a/instances/worker-1"} =>
      {200, GcpCompute.ReqStub.instance("worker-1")}
  })

assert {:ok, %GcpCompute.Instance{name: "worker-1"}} =
         GcpCompute.insert_instance_and_wait(config, name: "worker-1", machine_type: "e2-micro")

Copy it into your own test/support/ if you build on top of GcpCompute.

Simulating failures

  • API error — return a non-2xx status with the Google error envelope; it becomes {:error, %GcpCompute.Error{reason: :api_error, status: 403}}.
  • Transport error — return an exception from the adapter (e.g. %Mint.TransportError{reason: :econnrefused}); it becomes {:error, %GcpCompute.Error{reason: :transport}}.
  • Token failure — set token_provider: {GcpCompute.TokenProvider.Static, fn -> {:error, :no_creds} end}; the call short-circuits with {:error, %GcpCompute.Error{reason: :token_fetch_failed}} before any request is made.

Asserting telemetry

:telemetry.attach("t", [:gcp_compute, :request, :stop], &handler/4, self())
# ... make a call ...
assert_receive {:telemetry, _measurements, %{method: :get, result: :ok}}