{"id":15527561,"url":"https://github.com/elliotchance/dingo","last_synced_at":"2025-06-20T05:39:06.477Z","repository":{"id":54283852,"uuid":"189325838","full_name":"elliotchance/dingo","owner":"elliotchance","description":"🐺 Easy, fast and type-safe dependency injection for Go.","archived":false,"fork":false,"pushed_at":"2024-06-05T04:26:01.000Z","size":37,"stargazers_count":188,"open_issues_count":6,"forks_count":13,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-03T02:11:11.744Z","etag":null,"topics":["dependency-injection","factories","golang","mocking"],"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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-05-30T01:43:57.000Z","updated_at":"2025-03-15T17:45:07.000Z","dependencies_parsed_at":"2024-06-18T22:58:56.690Z","dependency_job_id":"8cc678fe-c287-4056-ad53-dcd11dfbd9bd","html_url":"https://github.com/elliotchance/dingo","commit_stats":{"total_commits":11,"total_committers":5,"mean_commits":2.2,"dds":0.4545454545454546,"last_synced_commit":"94bcaa41ea91d849f3d1e8240c9dfcdfc1259135"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/elliotchance/dingo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elliotchance%2Fdingo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elliotchance%2Fdingo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elliotchance%2Fdingo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elliotchance%2Fdingo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/elliotchance","download_url":"https://codeload.github.com/elliotchance/dingo/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elliotchance%2Fdingo/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260890581,"owners_count":23077863,"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":["dependency-injection","factories","golang","mocking"],"created_at":"2024-10-02T11:07:09.497Z","updated_at":"2025-06-20T05:39:01.460Z","avatar_url":"https://github.com/elliotchance.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# 🐺 dingo\n\nEasy, fast and type-safe dependency injection for Go.\n\n  * [Installation](#installation)\n  * [Building the Container](#building-the-container)\n  * [Configuring Package](#configuring-package)\n  * [Configuring Services](#configuring-services)\n    + [arguments](#arguments)\n    + [error](#error)\n    + [import](#import)\n    + [interface](#interface)\n    + [properties](#properties)\n    + [returns](#returns)\n    + [scope](#scope)\n    + [type](#type)\n  * [Using Services](#using-services)\n  * [Unit Testing](#unit-testing)\n  * [Practical Examples](#practical-examples)\n    + [Mocking the Clock](#mocking-the-clock)\n    + [Mocking Runtime Dependencies](#mocking-runtime-dependencies)\n\n## Installation\n\n```bash\ngo get -u github.com/elliotchance/dingo\n```\n\n## Building the Container\n\nBuilding or rebuilding the container is done with:\n\n```bash\ndingo\n```\n\nThe container is created from a file called `dingo.yml` in the same directory as\nwhere the `dingo` command is run. This should be the root of your\nmodule/repository.\n\nHere is an example of a `dingo.yml`:\n\n```yml\nservices:\n  SendEmail:\n    type: '*SendEmail'\n    interface: EmailSender\n    properties:\n      From: '\"hi@welcome.com\"'\n\n  CustomerWelcome:\n    type: '*CustomerWelcome'\n    returns: NewCustomerWelcome(@{SendEmail})\n```\n\nIt will generate a file called `dingo.go`. This must be committed with your\ncode.\n## Configuring Package\nThe root level `package` key describes the package name.\n\nDefault is the directory name is not enough because it may contain a command (package main). Find the first non-test file to get the real package name.\n\n## Configuring Services\n\nThe root level `services` key describes each of the services.\n\nThe name of the service follows the same naming conventions as Go, so service\nnames that start with a capital letter will be exported (available outside this\npackage).\n\nAll options described below are optional. However, you must provide either\n`type` or `interface`.\n\nAny option below that expects an expression can contain any valid Go code.\nReferences to other services and variables will be substituted automatically:\n\n- `@{SendEmail}` will inject the service named `SendEmail`.\n- `${DB_PASS}` will inject the environment variable `DB_PASS`.\n\n### arguments\n\nIf `arguments` is provided the service will be turned into a `func` so it can be\nused as a factory.\n\nThere is a full example in\n[Mocking Runtime Dependencies](#mocking-runtime-dependencies).\n\n### error\n\nIf `returns` provides two arguments (where the second one is the error) you must\ninclude an `error`. This is the expression when `err != nil`.\n\nExamples:\n\n- `error: panic(err)` - panic if an error occurs.\n- `error: return nil` - return a nil service if an error occurs.\n\n### import\n\nYou can provide explicit imports if you need to reference packages in\nexpressions (such as `returns`) that do not exist in `type` or `interface`.\n\nIf a package listed in `import` is already imported, either directly or\nindirectly, it value will be ignored.\n\nExample:\n\n```yml\nimport:\n  - 'github.com/aws/aws-sdk-go/aws/session'\n```\n\n### interface\n\nIf you need to replace this service with another `struct` type in unit tests you\nwill need to provide an `interface`. This will override `type` and must be\ncompatible with returned type of `returns`.\n\nExamples:\n\n- `interface: EmailSender` - `EmailSender` in this package.\n- `interface: io.Writer` - `Writer` in the `io` package.\n\n### properties\n\nIf provided, a map of case-sensitive properties to be set on the instance. Each\nof the properties is a Go expression.\n\nExample:\n\n```yml\nproperties:\n  From: \"hi@welcome.com\"\n  maxRetries: 10\n  emailer: '@{Emailer}'\n```\n\n### returns\n\nThe expression used to instantiate the service. You can provide any Go\nexpression here, including referencing other services and environment variables.\n\nThe `returns` can also return a function, since it is an expression. See `type`\nfor an example.\n\n### scope\n\nThe `scope` defines when a service should be created, or when it can be reused.\nIt must be one of the following values:\n\n- `prototype`: A new instance will be created whenever the service is requested\nor injected into another service as a dependency.\n\n- `container` (default): The instance will created once for this container, and\nthen it will be returned in future requests. This is sometimes called a\nsingleton, however the service will not be shared outside of the container.\n\n### type\n\nThe type returned by the `return` expression. You must provide a fully qualified\nname that includes the package name if the type does not belong to this package.\n\nExample\n\n```yml\ntype: '*github.com/go-redis/redis.Options'\n```\n\nThe `type` may also be a function. Functions can refer to other services in the\nsame embedded way:\n\n```yml\ntype: func () bool\nreturns: |\n  func () bool {\n    return @{Something}.IsReady()\n  }\n```\n\n## Using Services\n\nAs part of the generated file, `dingo.go`. There will be a module-level variable\ncalled `DefaultContainer`. This requires no initialization and can be used\nimmediately:\n\n```go\nfunc main() {\n\twelcomer := DefaultContainer.GetCustomerWelcome()\n\terr := welcomer.Welcome(\"Bob\", \"bob@smith.com\")\n\t// ...\n}\n```\n\n## Unit Testing\n\n**When unit testing you should not use the global `DefaultContainer`.** You\nshould create a new container:\n\n```go\ncontainer := NewContainer()\n```\n\nUnit tests can make any modifications to the new container, including overriding\nservices to provide mocks or other stubs:\n\n```go\nfunc TestCustomerWelcome_Welcome(t *testing.T) {\n\temailer := FakeEmailSender{}\n\temailer.On(\"Send\",\n\t\t\"bob@smith.com\", \"Welcome\", \"Hi, Bob!\").Return(nil)\n    \n\tcontainer := NewContainer()\n\tcontainer.SendEmail = emailer\n    \n\twelcomer := container.GetCustomerWelcome()\n\terr := welcomer.Welcome(\"Bob\", \"bob@smith.com\")\n\tassert.NoError(t, err)\n\temailer.AssertExpectations(t)\n}\n```\n\n## Practical Examples\n\n### Mocking the Clock\n\nCode that relies on time needs to be deterministic to be testable. Extracting\nthe clock as a service allows the whole time environment to be predictable for\nall services. It also has the added benefit that `Sleep()` is free when running\nunit tests.\n\nHere is a service, `WhatsTheTime`, that needs to use the current time:\n\n```yml\nservices:\n  Clock:\n    interface: github.com/jonboulle/clockwork.Clock\n    returns: clockwork.NewRealClock()\n\n  WhatsTheTime:\n    type: '*WhatsTheTime'\n    properties:\n      clock: '@{Clock}'\n```\n\n`WhatsTheTime` can now use this clock the same way you would use the `time`\npackage:\n\n```go\nimport (\n\t\"github.com/jonboulle/clockwork\"\n\t\"time\"\n)\n\ntype WhatsTheTime struct {\n\tclock clockwork.Clock\n}\n\nfunc (t *WhatsTheTime) InRFC1123() string {\n\treturn t.clock.Now().Format(time.RFC1123)\n}\n```\n\nThe unit test can substitute a fake clock for all services:\n\n```go\nfunc TestWhatsTheTime_InRFC1123(t *testing.T) {\n\tcontainer := NewContainer()\n\tcontainer.Clock = clockwork.NewFakeClock()\n\n\tactual := container.GetWhatsTheTime().InRFC1123()\n\tassert.Equal(t, \"Wed, 04 Apr 1984 00:00:00 UTC\", actual)\n}\n```\n\n### Mocking Runtime Dependencies\n\nOne situation that is tricky to write tests for is when you have the\ninstantiation inside a service because it needs some runtime state.\n\nLet's say you have a HTTP client that signs a request before sending it. The\nsigner can only be instantiated with the request, so we can't use traditional\ninjection:\n\n```go\ntype HTTPSignerClient struct{}\n\nfunc (c *HTTPSignerClient) Do(req *http.Request) (*http.Response, error) {\n\tsigner := NewSigner(req)\n\treq.Headers.Set(\"Authorization\", signer.Auth())\n\n\treturn http.DefaultClient.Do(req)\n}\n```\n\nThe `Signer` is not deterministic because it relies on the time:\n\n```go\ntype Signer struct {\n\treq *http.Request\n}\n\nfunc NewSigner(req *http.Request) *Signer {\n\treturn \u0026Signer{req: req}\n}\n\n// Produces something like \"Mon Jan 2 15:04:05 2006 POST\"\nfunc (signer *Signer) Auth() string {\n\treturn time.Now().Format(time.ANSIC) + \" \" + signer.req.Method\n}\n```\n\nUnlike mocking the clock (as in the previous tutorial) this time we need to keep\nthe logic of the signer, but verify the URL path sent to the signer. Of course,\nwe could manipulate or entirely replace the signer as well.\n\nServices can have `arguments` which turns them into factories. For example:\n\n```yml\nservices:\n  Signer:\n    type: '*Signer'\n    scope: prototype        # Create a new Signer each time\n    arguments:              # Define the dependencies at runtime.\n      req: '*http.Request'\n    returns: NewSigner(req) # Setup code can reference the runtime dependencies.\n\n  HTTPSignerClient:\n  \ttype: '*HTTPSignerClient'\n  \tproperties:\n  \t  CreateSigner: '@{Signer}' # Looks like a regular service, right?\n```\n\nDingo has transformed the service into a factory, using a function:\n\n```go\ntype HTTPSignerClient struct {\n\tCreateSigner func(req *http.Request) *Signer\n}\n\nfunc (c *HTTPSignerClient) Do(req *http.Request) (*http.Response, error) {\n\tsigner := c.CreateSigner(req)\n\treq.Headers.Set(\"Authorization\", signer.Auth())\n\n\treturn http.DefaultClient.Do(req)\n}\n```\n\nUnder test we can control this factory like any other service:\n\n```go\nfunc TestHTTPSignerClient_Do(t *testing.T) {\n\tcontainer := NewContainer()\n\tcontainer.Signer = func(req *http.Request) *Signer {\n\t\tassert.Equals(t, req.URL.Path, \"/foo\")\n\n\t\treturn NewSigner(req)\n\t}\n\n\tclient := container.GetHTTPSignerClient()\n\t_, err := client.Do(http.NewRequest(\"GET\", \"/foo\", nil))\n\tassert.NoError(t, err)\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felliotchance%2Fdingo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felliotchance%2Fdingo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felliotchance%2Fdingo/lists"}