{"id":40990040,"url":"https://github.com/ichiban/seams","last_synced_at":"2026-01-22T07:38:35.967Z","repository":{"id":57598750,"uuid":"216300218","full_name":"ichiban/seams","owner":"ichiban","description":"Go static analyzer that reports untestable function/method calls","archived":false,"fork":false,"pushed_at":"2019-11-03T13:49:21.000Z","size":8,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-06-20T07:29:52.215Z","etag":null,"topics":["go","golang","static-analysis","testing"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ichiban.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-10-20T03:07:19.000Z","updated_at":"2023-07-13T07:53:36.000Z","dependencies_parsed_at":"2022-08-30T04:41:14.146Z","dependency_job_id":null,"html_url":"https://github.com/ichiban/seams","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ichiban/seams","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ichiban%2Fseams","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ichiban%2Fseams/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ichiban%2Fseams/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ichiban%2Fseams/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ichiban","download_url":"https://codeload.github.com/ichiban/seams/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ichiban%2Fseams/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28658107,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-22T01:17:37.254Z","status":"online","status_checked_at":"2026-01-22T02:00:07.137Z","response_time":144,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","static-analysis","testing"],"created_at":"2026-01-22T07:38:35.322Z","updated_at":"2026-01-22T07:38:35.957Z","avatar_url":"https://github.com/ichiban.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# seams\n\nGo static analyzer that reports untestable function/method calls.\n\n## As a command\n\nAdd `seams` as a tool dependency in your `go.mod`:\n\n```shellsession\n$ go get -tool github.com/ichiban/seams/cmd/seams\n```\n\nThen, run via `go tool`:\n\n```shellsession\n$ go tool seams ./...\nmain.go:8:17: untestable function/method call: time.Parse\nmain.go:11:7: untestable function/method call: (time.Duration).Hours\nmain.go:11:7: untestable function/method call: (time.Time).Sub\nmain.go:11:16: untestable function/method call: time.Now\nmain.go:12:2: untestable function/method call: fmt.Printf\n```\n\nTo automatically fix the issues by introducing seam variables:\n\n```shellsession\n$ go tool seams -fix ./...\n```\n\n## As an `analysis.Analyzer`\n\nAdd the package as a dependency:\n\n```shellsession\n$ go get github.com/ichiban/seams\n```\n\nThen, include `seams.Analyzer` in your checker.\n\n```go\npackage main\n\nimport (\n\t\"golang.org/x/tools/go/analysis\"\n\t\"golang.org/x/tools/go/analysis/multichecker\"\n\t\"golang.org/x/tools/go/analysis/passes/nilfunc\"\n\t\"golang.org/x/tools/go/analysis/passes/printf\"\n\t\"golang.org/x/tools/go/analysis/passes/shift\"\n\n\t\"github.com/ichiban/seams\"\n)\n\nfunc main() {\n\tmultichecker.Main(\n\t\t// other analyzers of your choice\n\t\tnilfunc.Analyzer,\n\t\tprintf.Analyzer,\n\t\tshift.Analyzer,\n\n\t\tseams.Analyzer,\n\t)\n}\n```\n\n## Automatic fixing with `-fix`\n\nThe `-fix` flag automatically transforms untestable calls into testable ones by introducing seam variables.\n\n### Function calls\n\nFunction calls are transformed by creating a package-level variable and updating the call site:\n\n```go\n// Before\nfmt.Printf(\"Hello\")\n\n// After\nvar printf = fmt.Printf\nprintf(\"Hello\")\n```\n\n### Method calls\n\nMethod calls are transformed using method expressions. The receiver becomes the first argument:\n\n```go\n// Before\nvar b strings.Builder\nb.WriteString(\"text\")\n\n// After\nvar stringsBuilderWriteString = (*strings.Builder).WriteString\nstringsBuilderWriteString(\u0026b, \"text\")\n```\n\n### Naming conventions\n\n| Call type | Naming rule | Example |\n|-----------|-------------|---------|\n| Function | Lowercase first letter | `fmt.Printf` → `printf` |\n| Method | Package + Type + Method | `(*strings.Builder).WriteString` → `stringsBuilderWriteString` |\n\nIf a variable with the generated name already exists, the fix will reuse it without adding a new declaration.\n\n## What do you mean by untestable?\n\nThis static analyzer reports function/method calls that are of:\n\n- functions/methods defined in other packages,\n- not builtin functions,\n- not in tests, nor\n- in generated files\n\nbecause those function/method calls can't be replaced by test doubles in tests.\n\nFor example, `time.Now()` isn't testable because we can't change its behaviour for our tests.\nThough, `timeNow()` where `var timeNow = time.Now` is testable because we can change the behaviour in tests.\n\n### untestable example\n\n```go\npackage main\n\nimport (\n        \"fmt\"\n        \"time\"\n)\n\nvar date = must(time.Parse(time.RFC3339, \"2019-12-20T00:00:00+09:00\"))\n\nfunc main() {\n        d := date.Sub(time.Now()).Hours() / 24\n        fmt.Printf(\"%d days until Star Wars: The Rise of Skywalker\\n\", int(d))\n}\n\nfunc must(t time.Time, err error) time.Time {\n        if err != nil {\n                panic(err)\n        }\n        return t\n}\n```\n\n### testable example\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"time\"\n)\n\nvar (\n    timeParse         = time.Parse\n    timeDurationHours = time.Duration.Hours\n    timeTimeSub       = time.Time.Sub\n    timeNow           = time.Now\n    fmtPrintf         = fmt.Printf\n)\n\nvar date = must(timeParse(time.RFC3339, \"2019-12-20T00:00:00+09:00\"))\n\nfunc main() {\n    d := timeDurationHours(timeTimeSub(date, timeNow())) / 24\n    fmtPrintf(\"%d days until Star Wars: The Rise of Skywalker\\n\", int(d))\n}\n\nfunc must(t time.Time, err error) time.Time {\n    if err != nil {\n        panic(err)\n    }\n    return t\n}\n```\n\n```go\npackage main\n\nimport (\n    \"testing\"\n    \"time\"\n\n    \"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_main(t *testing.T) {\n    t.Run(\"on the day\", func(t *testing.T) {\n        assert := assert.New(t)\n\n        now, err := time.Parse(time.RFC3339, \"2019-12-20T12:00:00+09:00\")\n        assert.NoError(err)\n\n        n := timeNow\n        defer func() { timeNow = n }()\n        timeNow = func() time.Time {\n            return now\n        }\n\n        called := false\n        p := fmtPrintf\n        defer func() { fmtPrintf = p }()\n        fmtPrintf = func(format string, a ...interface{}) (int, error) {\n            assert.Equal(\"%d days until Star Wars: The Rise of Skywalker\\n\", format)\n            assert.Equal([]interface{}{0}, a)\n\n            called = true\n            return 0, nil\n        }\n\n        main()\n\n        assert.True(called)\n    })\n}\n```\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fichiban%2Fseams","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fichiban%2Fseams","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fichiban%2Fseams/lists"}