Question

How to Print in Go Tests with the testing Package

gotesting

Question

In Go, I am running a test that prints a value for debugging, but nothing appears in the output.

For example:

func TestPrintSomething(t *testing.T) {
    fmt.Println("Say hi")
}

When I run:

go test

I only see:

ok      command-line-arguments  0.004s

If I use t.Error() instead:

func TestPrintSomethingAgain(t *testing.T) {
    t.Error("Say hi")
}

then the message is printed, but the test fails.

How can I print debugging information in a Go test using the testing package without causing the test to fail?

Short Answer

By the end of this page, you will understand why fmt.Println output often does not appear during go test, how Go captures test output, and the correct ways to print debugging information using t.Log, t.Logf, and the -v flag.

Concept

In Go tests, output is handled differently from normal programs. When you run go test, the testing tool captures standard output and standard error produced during test execution.

This means:

  • fmt.Println() may not appear in the terminal for passing tests.
  • t.Error() prints because it records a test failure.
  • t.Log() and t.Logf() are the intended tools for test-specific debug messages.

Why does Go do this?

Because test output should usually stay clean unless:

  • a test fails, or
  • you explicitly ask for verbose output.

This makes large test suites easier to read. If every passing test printed debug text, the output would quickly become noisy.

The most important rule is:

  • Use t.Log() or t.Logf() for debugging inside tests.
  • Run tests with go test -v if you want to see log output from passing tests.

Example:

func TestPrintSomething(t *testing.T) {
    t.Log("Say hi")
}

Run with:

go test -v

Then you will see the message.

In short, the issue is not that printing is broken. It is that Go test intentionally hides most output unless it is relevant.

Mental Model

Think of go test as a quiet test supervisor.

  • fmt.Println() is like speaking into the room.
  • t.Log() is like writing a note into the test report.
  • t.Error() is like raising a red flag.

If everything passes, the supervisor keeps the room quiet and only gives a short summary. If you ask for a detailed report with -v, it shows the notes too. If something fails, the red flags are always shown.

So the key idea is: Go tests collect information first, then decide what to display.

Take Quiz

Syntax and Examples

The main tools are:

fmt.Println

func TestWithFmt(t *testing.T) {
    fmt.Println("debug message")
}
  • Writes to standard output.
  • Often hidden for passing tests.
  • May appear when tests fail or in some environments, but you should not rely on it for test logs.

t.Log

func TestWithLog(t *testing.T) {
    t.Log("debug message")
}
  • Adds a message to the test log.
  • Shown automatically when the test fails.
  • Shown for passing tests only with go test -v.

t.Logf

func TestWithLogf(t *testing.T) {
    name := "Gopher"
    t.Logf("hello, %s", name)
}
  • Same as t.Log, but supports formatting.
  • Useful when you want to print variable values.

t.Error

func TestWithError(t *testing.T) {
    t.Error("something went wrong")
}
  • Logs a message.
  • Marks the test as failed.
  • Use this only when the test should actually fail.

Best beginner-friendly example

package main

import "testing"

func TestGreeting(t *testing.T) {
    t.Log("Starting greeting test")

    got := "hello"
    want := "hello"

    if got != want {
        t.Errorf("got %q, want %q", got, want)
    }
}

Run normally:

go test

Output will usually be minimal if the test passes.

Run verbosely:

go test -v

Now you will see the t.Log message as well as the test execution details.

Step by Step Execution

Consider this test:

func TestPrintSomething(t *testing.T) {
    t.Log("Say hi")
}

Now run:

go test -v

What happens step by step:

  1. go test builds a temporary test binary.
  2. The test runner finds functions whose names start with Test and match the required signature.
  3. TestPrintSomething is executed.
  4. t.Log("Say hi") stores a log message in the test context.
  5. The test finishes without failure.
  6. Because -v was used, Go prints logs even for passing tests.

Typical output:

=== RUN   TestPrintSomething
    foo_test.go:6: Say hi
--- PASS: TestPrintSomething (0.00s)
PASS
ok      example  0.003s

Now compare with running without -v:

go test

Step 4 still happens, but since the test passes and verbose mode is off, the log is not shown.

If the test fails:

func TestPrintSomething(t *testing.T) {
    t.Log("Say hi")
    t.Fatal("forcing failure")
}

Then the log message is shown because the test failed, even without -v.

Real World Use Cases

This behavior is useful in real testing work.

Debugging a failing unit test

func TestAdd(t *testing.T) {
    got := 2 + 2
    t.Logf("computed result: %d", got)

    if got != 4 {
        t.Fatalf("expected 4, got %d", got)
    }
}

You can inspect intermediate values without permanently cluttering normal test output.

Inspecting API responses in tests

func TestAPIResponse(t *testing.T) {
    body := `{"status":"ok"}`
    t.Logf("response body: %s", body)
}

This helps when checking parsed JSON, headers, or mock server responses.

Verifying table-driven tests

func TestMultiply(t *testing.T) {
    tests := []struct {
        a, b int
        want int
    }{
        {2, 3, 6},
        {4, 5, 20},
    }

    for _, tc := range tests {
        t.Logf("testing %d * %d", tc.a, tc.b)
        got := tc.a * tc.b
        if got != tc.want {
            t.Fatalf("got %d, want %d", got, tc.want)
        }
    }
}

Logs can help identify which case was running when a failure occurred.

Temporary debugging during development

Developers often add t.Logf() while investigating a bug, run go test -v, then remove or keep the logs if they are still useful.

Take Quiz

Real Codebase Usage

In real Go codebases, developers usually prefer t.Log and t.Logf over fmt.Println inside tests.

Common patterns include:

Logging useful context before an assertion

func TestUserID(t *testing.T) {
    id := 42
    t.Logf("generated user id: %d", id)

    if id <= 0 {
        t.Fatal("user id must be positive")
    }
}

This helps when a CI failure needs quick diagnosis.

Using guard clauses with t.Fatal

func TestConfig(t *testing.T) {
    cfg, err := loadConfig()
    if err != nil {
        t.Fatalf("loadConfig failed: %v", err)
    }

    t.Logf("loaded config: %+v", cfg)
}

If setup fails, the test stops immediately.

Logging inside table-driven tests

func TestNormalize(t *testing.T) {
    tests := []struct {
        input string
        want  string
    }{
        {" Hello ", "hello"},
        {"Go", "go"},
    }

    for _, tc := range tests {
        t.Run(tc.input, func(t *testing.T) {
            t.Logf("input=%q", tc.input)
            got := normalize(tc.input)
            if got != tc.want {
                t.Fatalf("got %q, want %q", got, tc.want)
            }
        })
    }
}

This works especially well with subtests.

Avoiding noisy output in CI

Most teams keep normal test output quiet:

  • go test for routine runs
  • go test -v for investigation
  • t.Logf for contextual test logging
  • t.Errorf or t.Fatalf for actual failures

This keeps pipelines readable while still making debugging possible.

Common Mistakes

1. Using fmt.Println and expecting it to always appear

Broken expectation:

func TestSomething(t *testing.T) {
    fmt.Println("hello")
}

Why it is a problem:

  • go test may capture and hide this output for passing tests.

Better:

func TestSomething(t *testing.T) {
    t.Log("hello")
}

Run with:

go test -v

2. Using t.Error just to print a message

Broken usage:

func TestSomething(t *testing.T) {
    t.Error("debug value")
}

Why it is a problem:

  • t.Error marks the test as failed.
  • It should be used only when the test result is actually wrong.

Better:

func TestSomething(t *testing.T) {
    t.Log("debug value")
}

3. Forgetting the -v flag

If your test passes, this:

func TestSomething(t *testing.T) {
    t.Log("visible only in verbose mode for passing tests")
}

may show nothing with:

go test

Use:

go test -v

4. Confusing t.Error, t.Fail, t.Fatal, and t.Log

  • t.Log: log only
  • t.Error: log and fail
  • t.Fatal: log, fail, and stop the test immediately
  • t.Fail: fail without a message

5. Leaving too much debug noise in tests

Even though t.Log is useful, excessive logging can make failure output harder to read. Keep logs focused on values that help diagnose problems.

Comparisons

ToolPrints a messageFails the testStops execution immediatelyShown for passing tests by default
fmt.PrintlnYesNoNoUsually no
t.LogYesNoNoNo
t.LogfYesNoNoNo
t.ErrorYesYesNoYes, because test fails
t.ErrorfYesYesNoYes, because test fails
t.FatalYesYesYesYes, because test fails
t.FatalfYesYesYesYes, because test fails

fmt.Println vs t.Log

Aspectfmt.Printlnt.Log
Intended for testsNot specificallyYes
Connected to current testNoYes
Hidden/shown by test runner rulesLess predictableDesigned for that system
Best for debugging testsNot idealRecommended

Rule of thumb

  • Use t.Log / t.Logf for debugging information.
  • Use t.Error / t.Errorf when the test should fail.
  • Use t.Fatal / t.Fatalf when the test cannot continue.
Take Quiz

Cheat Sheet

// Log without failing
func TestExample(t *testing.T) {
    t.Log("message")
    t.Logf("value: %d", 42)
}
// Fail but continue
func TestExample(t *testing.T) {
    t.Error("something is wrong")
    t.Errorf("got %d, want %d", 1, 2)
}
// Fail and stop immediately
func TestExample(t *testing.T) {
    t.Fatal("cannot continue")
    t.Fatalf("bad input: %q", "")
}
# Default: quiet for passing tests
go test

# Verbose: show logs for passing tests
go test -v

Key rules:

  • t.Log and t.Logf are the correct ways to print debug info in tests.
  • Logs from passing tests are usually hidden unless you use -v.
  • Logs from failing tests are shown automatically.
  • t.Error and t.Fatal both fail the test.
  • t.Fatal stops the current test immediately.
  • fmt.Println is not the recommended way to log inside tests.

FAQ

Why does fmt.Println not show output in go test?

Because the Go test runner captures test output and usually hides it for passing tests to keep output clean.

How do I print debug information in a Go test without failing it?

Use t.Log() or t.Logf().

Why does t.Error() print the message?

Because it records a failure. Go shows failure output even when verbose mode is off.

How do I see t.Log() output for successful tests?

Run tests with:

go test -v

Should I use fmt.Println or t.Log in tests?

Prefer t.Log because it is designed for the testing package and works with Go's test reporting.

What is the difference between t.Error and t.Fatal?

t.Error marks the test as failed but continues running. t.Fatal marks the test as failed and stops that test immediately.

Are t.Log messages shown when a test fails?

Yes. Logs are shown for failed tests even without -v.

Can I use formatted output in test logs?

Yes. Use t.Logf() for formatted messages, just like fmt.Printf.

Take Quiz

Mini Project

Description

Create a small Go test file that demonstrates the difference between fmt.Println, t.Log, and t.Error. This helps you understand what appears during a normal go test run versus a verbose go test -v run.

Goal

Write tests that show how Go handles regular output, test logs, and test failures.

Requirements

Create one test that uses fmt.Println. Create one test that uses t.Log. Create one test that uses t.Error. Run the tests with both go test and go test -v and observe the differences.

Take Quiz

Keep learning

Related questions

View more