All Projects → elliotchance → Dingo

elliotchance / Dingo

Licence: mit
🐺 Easy, fast and type-safe dependency injection for Go.

Programming Languages

go
31211 projects - #10 most used programming language
golang
3204 projects

Projects that are alternatives of or similar to Dingo

hs-di
Haskell Dependency Injection
Stars: ✭ 14 (-90.91%)
Mutual labels:  dependency-injection, mocking
Hiboot
hiboot is a high performance web and cli application framework with dependency injection support
Stars: ✭ 150 (-2.6%)
Mutual labels:  dependency-injection
React Ioc
Hierarchical Dependency Injection with new React 16 Context API
Stars: ✭ 133 (-13.64%)
Mutual labels:  dependency-injection
Di
PSR-11 compatible DI container and injector
Stars: ✭ 141 (-8.44%)
Mutual labels:  dependency-injection
Disco
PSR-11 compatible Dependency Injection Container for PHP.
Stars: ✭ 135 (-12.34%)
Mutual labels:  dependency-injection
Fluentvalidation.blazor
Fluent Validation-powered Blazor component for validating standard <EditForm> 🌌 ✅
Stars: ✭ 140 (-9.09%)
Mutual labels:  dependency-injection
Appleguice
Effortless dependency injection framework for Objective-C
Stars: ✭ 131 (-14.94%)
Mutual labels:  dependency-injection
Castlemock
Castle Mock is a web application that provides the functionality to mock out RESTful APIs and SOAP web services.
Stars: ✭ 153 (-0.65%)
Mutual labels:  mocking
Dry Auto inject
Container-agnostic constructor injection mixin
Stars: ✭ 149 (-3.25%)
Mutual labels:  dependency-injection
Node Dependency Injection
The NodeDependencyInjection component allows you to standarize and centralize the way objects are constructed in your application.
Stars: ✭ 140 (-9.09%)
Mutual labels:  dependency-injection
Autofac.annotation
Autofac extras library for component registration via attributes 用注解来load autofac 摆脱代码或者xml配置和java的spring的注解注入一样的体验
Stars: ✭ 140 (-9.09%)
Mutual labels:  dependency-injection
Duckrails
Development tool to mock API endpoints quickly and easily (docker image available)
Stars: ✭ 1,690 (+997.4%)
Mutual labels:  mocking
Sactive Web
🚀 A dependency injection web framework for Node.js.
Stars: ✭ 143 (-7.14%)
Mutual labels:  dependency-injection
Fauxhai
Easily mock full ohai data
Stars: ✭ 134 (-12.99%)
Mutual labels:  mocking
Dig
A reflection based dependency injection toolkit for Go.
Stars: ✭ 2,255 (+1364.29%)
Mutual labels:  dependency-injection
Registry
Components as records of functions for Haskell
Stars: ✭ 132 (-14.29%)
Mutual labels:  dependency-injection
Microresolver
Extremely Fast Dependency Injection Library.
Stars: ✭ 138 (-10.39%)
Mutual labels:  dependency-injection
Hoverfly
Lightweight service virtualization/API simulation tool for developers and testers
Stars: ✭ 1,814 (+1077.92%)
Mutual labels:  mocking
Httpmocker
HttpMocker is a simple HTTP mocking library written in Kotlin to quickly and easily handle offline modes in your apps
Stars: ✭ 155 (+0.65%)
Mutual labels:  mocking
Package Builder
[READ-ONLY] Speed up your package DI containers integration and console apps to Symfony and Nette
Stars: ✭ 152 (-1.3%)
Mutual labels:  dependency-injection

🐺 dingo

Easy, fast and type-safe dependency injection for Go.

Installation

go get -u github.com/elliotchance/dingo

Building the Container

Building or rebuilding the container is done with:

dingo

The container is created from a file called dingo.yml in the same directory as where the dingo command is run. This should be the root of your module/repository.

Here is an example of a dingo.yml:

services:
  SendEmail:
    type: '*SendEmail'
    interface: EmailSender
    properties:
      From: '"[email protected]"'

  CustomerWelcome:
    type: '*CustomerWelcome'
    returns: NewCustomerWelcome(@{SendEmail})

It will generate a file called dingo.go. This must be committed with your code.

Configuring Package

The root level package key describes the package name.

Default 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.

Configuring Services

The root level services key describes each of the services.

The name of the service follows the same naming conventions as Go, so service names that start with a capital letter will be exported (available outside this package).

All options described below are optional. However, you must provide either type or interface.

Any option below that expects an expression can contain any valid Go code. References to other services and variables will be substituted automatically:

  • @{SendEmail} will inject the service named SendEmail.
  • ${DB_PASS} will inject the environment variable DB_PASS.

arguments

If arguments is provided the service will be turned into a func so it can be used as a factory.

There is a full example in Mocking Runtime Dependencies.

error

If returns provides two arguments (where the second one is the error) you must include an error. This is the expression when err != nil.

Examples:

  • error: panic(err) - panic if an error occurs.
  • error: return nil - return a nil service if an error occurs.

import

You can provide explicit imports if you need to reference packages in expressions (such as returns) that do not exist in type or interface.

If a package listed in import is already imported, either directly or indirectly, it value will be ignored.

Example:

import:
  - 'github.com/aws/aws-sdk-go/aws/session'

interface

If you need to replace this service with another struct type in unit tests you will need to provide an interface. This will override type and must be compatible with returned type of returns.

Examples:

  • interface: EmailSender - EmailSender in this package.
  • interface: io.Writer - Writer in the io package.

properties

If provided, a map of case-sensitive properties to be set on the instance. Each of the properties is a Go expression.

Example:

properties:
  From: "[email protected]"
  maxRetries: 10
  emailer: '@{Emailer}'

returns

The expression used to instantiate the service. You can provide any Go expression here, including referencing other services and environment variables.

The returns can also return a function, since it is an expression. See type for an example.

scope

The scope defines when a service should be created, or when it can be reused. It must be one of the following values:

  • prototype: A new instance will be created whenever the service is requested or injected into another service as a dependency.

  • container (default): The instance will created once for this container, and then it will be returned in future requests. This is sometimes called a singleton, however the service will not be shared outside of the container.

type

The type returned by the return expression. You must provide a fully qualified name that includes the package name if the type does not belong to this package.

Example

type: '*github.com/go-redis/redis.Options'

The type may also be a function. Functions can refer to other services in the same embedded way:

type: func () bool
returns: |
  func () bool {
    return @{Something}.IsReady()
  }

Using Services

As part of the generated file, dingo.go. There will be a module-level variable called DefaultContainer. This requires no initialization and can be used immediately:

func main() {
	welcomer := DefaultContainer.GetCustomerWelcome()
	err := welcomer.Welcome("Bob", "[email protected]")
	// ...
}

Unit Testing

When unit testing you should not use the global DefaultContainer. You should create a new container:

container := NewContainer()

Unit tests can make any modifications to the new container, including overriding services to provide mocks or other stubs:

func TestCustomerWelcome_Welcome(t *testing.T) {
	emailer := FakeEmailSender{}
	emailer.On("Send",
		"[email protected]", "Welcome", "Hi, Bob!").Return(nil)
    
	container := NewContainer()
	container.SendEmail = emailer
    
	welcomer := container.GetCustomerWelcome()
	err := welcomer.Welcome("Bob", "[email protected]")
	assert.NoError(t, err)
	emailer.AssertExpectations(t)
}

Practical Examples

Mocking the Clock

Code that relies on time needs to be deterministic to be testable. Extracting the clock as a service allows the whole time environment to be predictable for all services. It also has the added benefit that Sleep() is free when running unit tests.

Here is a service, WhatsTheTime, that needs to use the current time:

services:
  Clock:
    interface: github.com/jonboulle/clockwork.Clock
    returns: clockwork.NewRealClock()

  WhatsTheTime:
    type: '*WhatsTheTime'
    properties:
      clock: '@{Clock}'

WhatsTheTime can now use this clock the same way you would use the time package:

import (
	"github.com/jonboulle/clockwork"
	"time"
)

type WhatsTheTime struct {
	clock clockwork.Clock
}

func (t *WhatsTheTime) InRFC1123() string {
	return t.clock.Now().Format(time.RFC1123)
}

The unit test can substitute a fake clock for all services:

func TestWhatsTheTime_InRFC1123(t *testing.T) {
	container := NewContainer()
	container.Clock = clockwork.NewFakeClock()

	actual := container.GetWhatsTheTime().InRFC1123()
	assert.Equal(t, "Wed, 04 Apr 1984 00:00:00 UTC", actual)
}

Mocking Runtime Dependencies

One situation that is tricky to write tests for is when you have the instantiation inside a service because it needs some runtime state.

Let's say you have a HTTP client that signs a request before sending it. The signer can only be instantiated with the request, so we can't use traditional injection:

type HTTPSignerClient struct{}

func (c *HTTPSignerClient) Do(req *http.Request) (*http.Response, error) {
	signer := NewSigner(req)
	req.Headers.Set("Authorization", signer.Auth())

	return http.DefaultClient.Do(req)
}

The Signer is not deterministic because it relies on the time:

type Signer struct {
	req *http.Request
}

func NewSigner(req *http.Request) *Signer {
	return &Signer{req: req}
}

// Produces something like "Mon Jan 2 15:04:05 2006 POST"
func (signer *Signer) Auth() string {
	return time.Now().Format(time.ANSIC) + " " + signer.req.Method
}

Unlike mocking the clock (as in the previous tutorial) this time we need to keep the logic of the signer, but verify the URL path sent to the signer. Of course, we could manipulate or entirely replace the signer as well.

Services can have arguments which turns them into factories. For example:

services:
  Signer:
    type: '*Signer'
    scope: prototype        # Create a new Signer each time
    arguments:              # Define the dependencies at runtime.
      req: '*http.Request'
    returns: NewSigner(req) # Setup code can reference the runtime dependencies.

  HTTPSignerClient:
  	type: '*HTTPSignerClient'
  	properties:
  	  CreateSigner: '@{Signer}' # Looks like a regular service, right?

Dingo has transformed the service into a factory, using a function:

type HTTPSignerClient struct {
	CreateSigner func(req *http.Request) *Signer
}

func (c *HTTPSignerClient) Do(req *http.Request) (*http.Response, error) {
	signer := c.CreateSigner(req)
	req.Headers.Set("Authorization", signer.Auth())

	return http.DefaultClient.Do(req)
}

Under test we can control this factory like any other service:

func TestHTTPSignerClient_Do(t *testing.T) {
	container := NewContainer()
	container.Signer = func(req *http.Request) *Signer {
		assert.Equals(t, req.URL.Path, "/foo")

		return NewSigner(req)
	}

	client := container.GetHTTPSignerClient()
	_, err := client.Do(http.NewRequest("GET", "/foo", nil))
	assert.NoError(t, err)
}
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].