Ginkgo provides built-in reporters for generating test reports in various formats (JSON, JUnit XML, Teamcity) and supports custom reporters through the Reporter interface.
Generates a JSON-formatted test report.
func GenerateJSONReport(report types.Report, destination string) errorParameters:
report: Complete test suite reportdestination: Output file pathExample:
import "github.com/onsi/ginkgo/v2/reporters"
ReportAfterSuite(func(report Report) {
err := reporters.GenerateJSONReport(report, "results.json")
if err != nil {
fmt.Printf("Failed to generate JSON report: %v\n", err)
}
})Generates a report in Go's test JSON format (compatible with go test -json).
func GenerateGoTestJSONReport(report types.Report, destination string) errorExample:
ReportAfterSuite(func(report Report) {
err := reporters.GenerateGoTestJSONReport(report, "go-test.json")
if err != nil {
fmt.Printf("Failed to generate Go JSON report: %v\n", err)
}
})Generates a JUnit XML report.
func GenerateJUnitReport(report types.Report, dst string) errorExample:
ReportAfterSuite(func(report Report) {
err := reporters.GenerateJUnitReport(report, "junit.xml")
if err != nil {
fmt.Printf("Failed to generate JUnit report: %v\n", err)
}
})Generates a JUnit XML report with custom configuration.
func GenerateJUnitReportWithConfig(
report types.Report,
dst string,
config JunitReportConfig,
) errorExample:
config := reporters.JunitReportConfig{
OmitTimelinesForSpecState: types.SpecStatePassed,
OmitCapturedStdOutErr: true,
OmitSpecLabels: false,
}
ReportAfterSuite(func(report Report) {
err := reporters.GenerateJUnitReportWithConfig(
report, "junit.xml", config)
if err != nil {
fmt.Printf("Failed to generate JUnit report: %v\n", err)
}
})Generates a Teamcity-formatted report.
func GenerateTeamcityReport(report types.Report, dst string) errorExample:
ReportAfterSuite(func(report Report) {
err := reporters.GenerateTeamcityReport(report, "teamcity.txt")
if err != nil {
fmt.Printf("Failed to generate Teamcity report: %v\n", err)
}
})When running tests in parallel, Ginkgo generates separate reports for each process. These functions merge and cleanup the individual reports.
Merges multiple JSON reports into one and deletes the originals.
func MergeAndCleanupJSONReports(
sources []string,
destination string,
) ([]string, error)Parameters:
sources: Paths to individual JSON report filesdestination: Path for merged reportReturns:
Example:
merged, err := reporters.MergeAndCleanupJSONReports(
[]string{"report-1.json", "report-2.json", "report-3.json"},
"merged-report.json",
)
if err != nil {
panic(err)
}
fmt.Printf("Merged %d reports\n", len(merged))Merges multiple Go test JSON reports.
func MergeAndCleanupGoTestJSONReports(
sources []string,
destination string,
) ([]string, error)Merges multiple JUnit XML reports.
func MergeAndCleanupJUnitReports(
sources []string,
dst string,
) ([]string, error)Merges multiple Teamcity reports.
func MergeAndCleanupTeamcityReports(
sources []string,
dst string,
) ([]string, error)Configuration for JUnit report generation.
type JunitReportConfig struct {
OmitTimelinesForSpecState types.SpecState
OmitFailureMessageAttr bool
OmitCapturedStdOutErr bool
OmitSpecLabels bool
OmitSpecSemVerConstraints bool
OmitLeafNodeType bool
OmitSuiteSetupNodes bool
}Fields:
OmitTimelinesForSpecState: Don't include timelines for specs in these statesOmitFailureMessageAttr: Don't include message attribute in failure elementsOmitCapturedStdOutErr: Don't include captured stdout/stderrOmitSpecLabels: Don't include labels in spec namesOmitSpecSemVerConstraints: Don't include version constraints in spec namesOmitLeafNodeType: Don't include node type in spec namesOmitSuiteSetupNodes: Don't create test case entries for suite setup nodesExample:
// Minimal JUnit report (faster generation for large suites)
config := reporters.JunitReportConfig{
OmitTimelinesForSpecState: types.SpecStatePassed | types.SpecStateSkipped,
OmitCapturedStdOutErr: true,
OmitSpecLabels: true,
OmitSuiteSetupNodes: true,
}These types represent the structure of JUnit XML reports.
Root element of JUnit XML report.
type JUnitTestSuites struct {
XMLName xml.Name
Tests int
Disabled int
Errors int
Failures int
Time float64
TestSuites []JUnitTestSuite
}Individual test suite in JUnit XML.
type JUnitTestSuite struct {
Name string
Package string
Tests int
Disabled int
Skipped int
Errors int
Failures int
Time float64
Timestamp string
Properties JUnitProperties
TestCases []JUnitTestCase
}Individual test case in JUnit XML.
type JUnitTestCase struct {
Name string
Classname string
Status string
Time float64
Owner string
Skipped *JUnitSkipped
Error *JUnitError
Failure *JUnitFailure
SystemOut string
SystemErr string
}Properties collection in JUnit XML.
type JUnitProperties struct {
Properties []JUnitProperty
}Methods:
func (props JUnitProperties) WithName(name string) stringIndividual property.
type JUnitProperty struct {
Name string
Value string
}Skipped test information.
type JUnitSkipped struct {
Message string
}Failure information in JUnit XML.
type JUnitFailure struct {
Message string
Type string
Description string
}Error/panic information in JUnit XML.
type JUnitError struct {
Message string
Type string
Description string
}Interface for implementing custom reporters.
type Reporter interface {
SuiteWillBegin(report types.Report)
WillRun(report types.SpecReport)
DidRun(report types.SpecReport)
SuiteDidEnd(report types.Report)
EmitFailure(state types.SpecState, failure types.Failure)
EmitProgressReport(progressReport types.ProgressReport)
EmitReportEntry(entry types.ReportEntry)
EmitSpecEvent(event types.SpecEvent)
}Example Custom Reporter:
type CustomReporter struct {
startTime time.Time
specCount int
}
func (r *CustomReporter) SuiteWillBegin(report types.Report) {
r.startTime = time.Now()
fmt.Printf("Starting suite: %s\n", report.SuiteDescription)
fmt.Printf("Will run %d specs\n", report.PreRunStats.SpecsThatWillRun)
}
func (r *CustomReporter) WillRun(report types.SpecReport) {
fmt.Printf("Running: %s\n", report.FullText())
}
func (r *CustomReporter) DidRun(report types.SpecReport) {
r.specCount++
if report.Failed() {
fmt.Printf("FAILED: %s\n", report.FullText())
fmt.Printf(" Error: %s\n", report.Failure.Message)
} else {
fmt.Printf("PASSED: %s (%v)\n", report.FullText(), report.RunTime)
}
}
func (r *CustomReporter) SuiteDidEnd(report types.Report) {
duration := time.Since(r.startTime)
fmt.Printf("Suite completed in %v\n", duration)
fmt.Printf("Ran %d specs\n", r.specCount)
fmt.Printf("Success: %v\n", report.SuiteSucceeded)
}
func (r *CustomReporter) EmitFailure(
state types.SpecState,
failure types.Failure,
) {
fmt.Printf("Failure emitted: %s\n", failure.Message)
}
func (r *CustomReporter) EmitProgressReport(
progressReport types.ProgressReport,
) {
fmt.Printf("Progress: %s\n", progressReport.CurrentNodeText)
}
func (r *CustomReporter) EmitReportEntry(entry types.ReportEntry) {
fmt.Printf("Report Entry: %s = %v\n", entry.Name, entry.Value)
}
func (r *CustomReporter) EmitSpecEvent(event types.SpecEvent) {
fmt.Printf("Spec Event: %s\n", event.Message)
}
// Use the custom reporter
func TestMyPackage(t *testing.T) {
RegisterFailHandler(Fail)
customReporter := &CustomReporter{}
RunSpecs(t, "My Suite", Label("custom"), customReporter)
}A no-operation reporter that does nothing. Useful as a base for custom reporters.
type NoopReporter struct{}Methods:
func (r NoopReporter) SuiteWillBegin(report types.Report)
func (r NoopReporter) WillRun(report types.SpecReport)
func (r NoopReporter) DidRun(report types.SpecReport)
func (r NoopReporter) SuiteDidEnd(report types.Report)
func (r NoopReporter) EmitFailure(state types.SpecState, failure types.Failure)
func (r NoopReporter) EmitProgressReport(progressReport types.ProgressReport)
func (r NoopReporter) EmitReportEntry(entry types.ReportEntry)
func (r NoopReporter) EmitSpecEvent(event types.SpecEvent)Example:
type MyReporter struct {
reporters.NoopReporter // Embed to inherit no-op methods
}
// Only override methods you care about
func (r *MyReporter) DidRun(report types.SpecReport) {
if report.Failed() {
sendAlertToSlack(report)
}
}Ginkgo's default console reporter.
type DefaultReporter struct{}Constructors:
func NewDefaultReporter(
conf types.ReporterConfig,
writer io.Writer,
) *DefaultReporter
func NewDefaultReporterUnderTest(
conf types.ReporterConfig,
writer io.Writer,
) *DefaultReporterMethods:
func (r *DefaultReporter) SuiteWillBegin(report types.Report)
func (r *DefaultReporter) WillRun(report types.SpecReport)
func (r *DefaultReporter) DidRun(report types.SpecReport)
func (r *DefaultReporter) SuiteDidEnd(report types.Report)
func (r *DefaultReporter) EmitFailure(state types.SpecState, failure types.Failure)
func (r *DefaultReporter) EmitProgressReport(report types.ProgressReport)
func (r *DefaultReporter) EmitReportEntry(entry types.ReportEntry)
func (r *DefaultReporter) EmitSpecEvent(event types.SpecEvent)Renders a spec timeline as a formatted string.
func RenderTimeline(spec types.SpecReport, noColor bool) stringExample:
ReportAfterEach(func(report SpecReport) {
if report.Failed() {
timeline := reporters.RenderTimeline(report, false)
fmt.Println(timeline)
}
})Calls a V1-style custom reporter (for backward compatibility).
func ReportViaDeprecatedReporter(
reporter DeprecatedReporter,
report types.Report,
)V1 reporter interface (deprecated).
type DeprecatedReporter interface {
SuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary)
BeforeSuiteDidRun(setupSummary *types.SetupSummary)
SpecWillRun(specSummary *types.SpecSummary)
SpecDidComplete(specSummary *types.SpecSummary)
AfterSuiteDidRun(setupSummary *types.SetupSummary)
SuiteDidEnd(summary *types.SuiteSummary)
}func TestMyPackage(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "My Package Suite")
}
var _ = ReportAfterSuite(func(report Report) {
// Generate all report formats
reporters.GenerateJSONReport(report, "results.json")
reporters.GenerateJUnitReport(report, "junit.xml")
reporters.GenerateTeamcityReport(report, "teamcity.txt")
})
// Or use CLI flags:
// ginkgo --json-report=results.json --junit-report=junit.xmlvar _ = ReportAfterSuite(func(report Report) {
// Only generate detailed reports on failure
if !report.SuiteSucceeded {
config := reporters.JunitReportConfig{
OmitTimelinesForSpecState: types.SpecStateNone,
OmitCapturedStdOutErr: false,
}
reporters.GenerateJUnitReportWithConfig(
report, "detailed-junit.xml", config)
} else {
// Minimal report on success
config := reporters.JunitReportConfig{
OmitTimelinesForSpecState: types.SpecStatePassed,
OmitCapturedStdOutErr: true,
}
reporters.GenerateJUnitReportWithConfig(
report, "minimal-junit.xml", config)
}
})type MetricsReporter struct {
reporters.NoopReporter
metrics map[string]time.Duration
}
func NewMetricsReporter() *MetricsReporter {
return &MetricsReporter{
metrics: make(map[string]time.Duration),
}
}
func (r *MetricsReporter) DidRun(report types.SpecReport) {
r.metrics[report.FullText()] = report.RunTime
}
func (r *MetricsReporter) SuiteDidEnd(report types.Report) {
// Send metrics to monitoring system
for spec, duration := range r.metrics {
sendMetric("test.duration", duration, map[string]string{
"spec": spec,
})
}
}
func TestWithMetrics(t *testing.T) {
RegisterFailHandler(Fail)
metricsReporter := NewMetricsReporter()
RunSpecs(t, "Suite with Metrics", metricsReporter)
}