{"id":15527553,"url":"https://github.com/elliotchance/tf","last_synced_at":"2025-04-14T00:19:04.640Z","repository":{"id":57491349,"uuid":"143796385","full_name":"elliotchance/tf","owner":"elliotchance","description":"✔️ tf is a microframework for parameterized testing of functions and HTTP in Go.","archived":false,"fork":false,"pushed_at":"2019-05-30T05:42:29.000Z","size":21,"stargazers_count":136,"open_issues_count":3,"forks_count":3,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-27T14:21:30.810Z","etag":null,"topics":["assertions","go","golang","http","micro-framework","testing","testing-framework"],"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/elliotchance.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-08-07T00:06:41.000Z","updated_at":"2025-02-11T21:34:12.000Z","dependencies_parsed_at":"2022-08-30T04:00:37.043Z","dependency_job_id":null,"html_url":"https://github.com/elliotchance/tf","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elliotchance%2Ftf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elliotchance%2Ftf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elliotchance%2Ftf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elliotchance%2Ftf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/elliotchance","download_url":"https://codeload.github.com/elliotchance/tf/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248799984,"owners_count":21163404,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["assertions","go","golang","http","micro-framework","testing","testing-framework"],"created_at":"2024-10-02T11:07:06.004Z","updated_at":"2025-04-14T00:19:04.617Z","avatar_url":"https://github.com/elliotchance.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# tf\n\ntf is a microframework for parametrized testing of functions and HTTP in Go.\n\n- [Functions](#functions)\n  * [Grouping](#grouping)\n  * [Struct Functions](#struct-functions)\n- [HTTP Testing](#http-testing)\n  * [Client](#client)\n  * [Server](#server)\n- [Environment Variables](#environment-variables)\n\n# Functions\n\nIt offers a simple and intuitive syntax for tests by wrapping the function:\n\n```go\n// Remainder returns the quotient and remainder from dividing two integers.\nfunc Remainder(a, b int) (int, int) {\n    return a / b, a % b\n}\n\nfunc TestRemainder(t *testing.T) {\n    Remainder := tf.Function(t, Remainder)\n\n    Remainder(10, 3).Returns(3, 1)\n    Remainder(10, 2).Returns(5, 0)\n    Remainder(17, 7).Returns(2, 3)\n}\n```\n\nAssertions are performed with [testify](https://github.com/stretchr/testify). If\nan assertion fails it will point to the correct line so you do not need to\nexplicitly label tests.\n\nThe above test will output (in verbose mode):\n\n```\n=== RUN   TestRemainder\n--- PASS: TestRemainder (0.00s)\n=== RUN   TestRemainder/Remainder#1\n--- PASS: TestRemainder/Remainder#1 (0.00s)\n=== RUN   TestRemainder/Remainder#2\n--- PASS: TestRemainder/Remainder#2 (0.00s)\n=== RUN   TestRemainder/Remainder#3\n--- PASS: TestRemainder/Remainder#3 (0.00s)\nPASS\n```\n\n## Grouping\n\nUse `NamedFunction` to specify a custom name for the function/group:\n\n```go\nfunc TestNamedSum(t *testing.T) {\n\tSum := tf.NamedFunction(t, \"Sum1\", Item.Add)\n\n\tSum(Item{1.3, 4.5}, 3.4).Returns(9.2)\n\tSum(Item{1.3, 4.6}, 3.5).Returns(9.4)\n\n\tSum = tf.NamedFunction(t, \"Sum2\", Item.Add)\n\n\tSum(Item{1.3, 14.5}, 3.4).Returns(19.2)\n\tSum(Item{21.3, 4.6}, 3.5).Returns(29.4)\n}\n```\n\n## Struct Functions\n\nYou can test struct functions by providing the struct value as the first\nparameter followed by any function arguments, if any.\n\n```go\ntype Item struct {\n\ta, b float64\n}\n\nfunc (i Item) Add(c float64) float64 {\n\treturn i.a + i.b + c\n}\n\nfunc TestItem_Add(t *testing.T) {\n\tSum := tf.Function(t, Item.Add)\n\n\tSum(Item{1.3, 4.5}, 3.4).Returns(9.2)\n}\n```\n\n# HTTP Testing\n\n## Client\n\nSuper easy HTTP testing by using the ServeHTTP function. This means that you do\nnot have to run the server and it is compatible with all HTTP libraries and\nframeworks but has all the functionality of the server itself.\n\nThe simplest example is to use the default muxer in the `http` package:\n\n```go\nhttp.HandleFunc(\"/hello\", func(w http.ResponseWriter, r *http.Request) {\n    fmt.Fprint(w, \"Hello, World!\")\n})\n```\n\nAnd now we can write some tests:\n\n```go\nfunc TestHTTPRouter(t *testing.T) {\n\trun := tf.ServeHTTP(t, http.DefaultServeMux.ServeHTTP)\n\n\trun(\u0026tf.HTTPTest{\n\t\tPath:         \"/hello\",\n\t\tStatus:       http.StatusOK,\n\t\tResponseBody: strings.NewReader(\"Hello, World!\"),\n\t})\n\n\trun(\u0026tf.HTTPTest{\n\t\tPath:   \"/world\",\n\t\tStatus: http.StatusNotFound,\n\t})\n}\n```\n\nIt is compatible with all HTTP frameworks because they must all expose a\nServeHTTP which is the entry point for the request router/handler.\n\nThere are many more options for HTTPTest. Some HTTP tests require multiple\noperations, you can use `MultiHTTPTest` for this:\n\n```go\nrun(\u0026tf.MultiHTTPTest{\n\tSteps: []*tf.HTTPTest{\n\t\t{\n\t\t\tPath:        \"/save\",\n\t\t\tMethod:      http.MethodPut,\n\t\t\tRequestBody: strings.NewReader(`{\"foo\":\"bar\"}`),\n\t\t\tStatus:      http.StatusCreated,\n\t\t},\n\t\t{\n\t\t\tPath:         \"/fetch\",\n\t\t\tMethod:       http.MethodGet,\n\t\t\tStatus:       http.StatusOK,\n\t\t\tResponseBody: strings.NewReader(`{\"foo\":\"bar\"}`),\n\t\t},\n\t},\n})\n```\n\nEach step will only proceed if the previous step was successful.\n\n## Server\n\nSometimes you need to mock HTTP servers where the only option is to provide a\nURL endpoint through to your test. That is, when you do not have direct access\nto the router, or it's impractical to inject the behavior.\n\nFor case this you can use a `HTTPServer`:\n\n```go\n// 0 means to use a random port, or you can provide your own.\nserver := tf.StartHTTPServer(0).\n\tAddHandlers(map[string]http.HandlerFunc{\n\t\t\"/hi\": func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Write([]byte(`hello`))\n\t\t},\n\t\t\"/easy\": tf.HTTPJSONResponse(200, []int{1, 2, 3}),\n\t})\n\n// Always remember to tear down the resources when the test ends.\ndefer server.Shutdown()\n\n// Your test code here...\nserver.Endpoint() // http://localhost:61223\n```\n\nUsing a real HTTP server has some benefits:\n\n1. It's isolated. That means it does not interfere in anyway with the global\nHTTP server.\n\n2. It can be used in parallel. You can either share the same `HTTPServer` across\nmany tests (such as in `TestMain`), or create one for each test in parallel.\nProviding `0` for the port (as in the example above) will ensure that it always\nselects an unused random port.\n\n3. It mutable. After creating and starting the `HTTPServer` you can add/remove\nhandlers. This is useful when most tests need a base logic, but some cases need\nto return special under specific scenarios.\n\nYou can create your own handlers, of course, but there are a few common ones\nthat also ship with `tf`:\n\n- `HTTPEmptyResponse(statusCode int)`\n- `HTTPStringResponse(statusCode int, body string)`\n- `HTTPJSONResponse(statusCode int, body interface{})`\n\n# Environment Variables\n\n`SetEnv` sets an environment variable and returns a reset function to ensure\nthe environment is always returned to it's previous state:\n\n```go\nresetEnv := tf.SetEnv(t, \"HOME\", \"/somewhere/else\")\ndefer resetEnv()\n```\n\nIf you would like to set multiple environment variables, you can use `SetEnvs`\nin the same way:\n\n```go\nresetEnv := tf.SetEnvs(t, map[string]string{\n    \"HOME\":  \"/somewhere/else\",\n    \"DEBUG\": \"on\",\n})\ndefer resetEnv()\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felliotchance%2Ftf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felliotchance%2Ftf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felliotchance%2Ftf/lists"}