2

I'm looking to have my revel controllers use various services, which I mock out for unit tests. I'm new to Go; in C# I'd inject them using dependency injection. Is there a common way to do this in revel?

It seems like the best way I've found is to initialise the real services in the controller's Before() method (possibly using a method solved by wire), and to set the mock versions in the the test's Before() method. Or is there a better way?

Stuart Moore
  • 681
  • 5
  • 32
  • http://github.com/jwells131313/dargo is a dependency injection system for go that handles adding in test mocks that override the "real" service for unit testing. It might be what you are looking for – jwells131313 Oct 29 '18 at 15:08
  • Thanks. What I'm struggling with is how to couple that with Revel - i.e. what is the correct place to put the code that obtains the dependencies, so that Revel executes it as part of creating a constructor – Stuart Moore Oct 29 '18 at 15:33
  • Unfortunately I can't help with that, I don't know anything about revel. Dargo does allow for constructor functions for services and you could use that constructor function to construct your revel services maybe? – jwells131313 Oct 29 '18 at 15:37

2 Answers2

3

I use a filter to inject dependencies.

The filter tests if the controller implements specific interfaces and stuffs the correct dependency in. Here's an example that inserts a database-related dependency:

func DependencyFilter(c *revel.Controller, filterChain []revel.Filter) {
    if ctrl, ok := c.AppController.(DataServiceController); ok {
        ctrl.SetDataService(<your dependency>)
    }

    // Different dependencies could be injected here:
    //if ctrl, ok := c.AppController.(FooController); ok {
    //  ctrl.SetFooService(<your dependency>)
    //}

    // Call the next filter
    if len(filterChain) > 0 {
        filterChain[0](c, filterChain[1:])
    }
}

Where DataServiceController is:

type DataServiceController interface {
    SetDataService(ds services.DataService)
}

I inserted my filter as the penultimate entry in init.go:

revel.Filters = []revel.Filter{
    revel.PanicFilter,             // Recover from panics and display an error page instead.
    // ...
    DependencyFilter,              // Add dependencies
    revel.ActionInvoker,           // Invoke the action.
}

Most of my controllers need the same dependencies, so I have a base controller that they all embed:

type BaseController struct {
    *revel.Controller
    DataService services.DataService
}

func (c *BaseController) SetDataService(ds services.DataService) {
    c.DataService = ds
} 

So my concrete controllers look like this:

type Home struct {
    BaseController
}

func (c Home) Index() revel.Result {
    // ...
}

There might be better ways, but this is my approach.

Duncan Jones
  • 67,400
  • 29
  • 193
  • 254
0

In fact there's a lot of DI systems for GO. I've searched for several, tried to use and finally have chosen one with some patches for more convenience. Usage is quite easy:

package dependency

import (
   "fmt"

   "github.com/lisitsky/inject"
)

func init() {
   inject.Provide(NewStringer)
}

type stringer struct{}

func (s stringer) String() string {
    return "Hello, World"
}

func NewStringer() fmt.Stringer {
    return stringer{}
}

On a side accepting dependency (main.go):

package main


import (
   "fmt"

   "github.com/lisitsky/inject"

   _ "github.com/lisitsky/inject/examples/simple/dependency"
)

var ( 
   str fmt.Stringer
)

func main() {
   inject.Construct(&str)
   fmt.Println("My Stringer is:", str)
}

Also it supports delayed initialization:

func main() {
   // define variables to be constructed later
   inject.ConstructLater(&str)

   // define dependency providers
   inject.Provide(NewStringer)

   // finalize construction - all DI variables would be initialized at one call
   injector.FinishConstruct()

   fmt.Println("My Stringer is:", str)
}
Eugene Lisitsky
  • 12,113
  • 5
  • 38
  • 59
  • 1
    Thanks - I'm specifically looking for a suggested way to use DI with Revel. It seems there isn't one - the best I have come up with is getting the dependencies on the Before() method of each controller, which works but may lead to unnecessary work, as it occurs for every request rather than once per controller. – Stuart Moore Nov 01 '18 at 11:01