Saturday, July 28, 2018

How do I mock a function from another package without using dependency injection?

Leave a Comment

Somewhat of a golang beginner, but I've worked with testing frameworks before. How do I go about mocking out and faking what a dependent method returns without injecting the dependency? The reason why I don't want to use dependency injection is because there are many external package methods that are being used and injecting all of the methods in the constructor is unwieldy.

I've searched for this online/stackoverflow and the solution is to always use dependency injection. Sometimes that is not a viable option.

Here's what I'm trying to do code-wise:

b/b_test.go

package b  func TestResults(t *testing.T) {     t.Run("Test", func(t *testing.T) {         b := NewB()         // How do I mock out and fake a.DoSomething() to be         // "complete" instead of whats in the code right now?         result = b.Results()         assert.Equal(t, "complete", result)                 } } 

b/b.go

package b  import "a"  type B struct {}  func NewB() B {     return &B{} }  func (b B) Results() {     return a.DoSomething() } 

a/a.go

package a  func DoSomething() {     return "done" } 

Thanks!

3 Answers

Answers 1

One way to do so would be to create a variable with the function you want to call, so include the following in b.go:

doSomething := func() { a.DoSomething() } func (b B) Results() {     return a.DoSomething() } 

Now in b_test.go you can do this:

func TestPrintResults(t *testing.T) {     t.Run("Test", func(t *testing.T) {         origDoSomething := doSomething         defer func() { doSomething = origDoSomething }         doSomething = func() {           // Insert fake implementation here         }         b := NewB()         result = b.Results()         assert.Equal(t, "complete", result)                 } } 

Answers 2

You can use conditional compilation with build tags

a/a.go

// +build !mock  package a func DoSomething() {     return "done" } 

a/a_mock.go

// +build mock  package a func DoSomething() {  // Insert fake implementation here     return "complete" } 

$ go test -tags mock

Answers 3

I'm not sure if I don't understand your objection to dependency injection, but interfaces can be used to make dependency injection relatively painless. In particular existing code does not need to be modified.

You could try aliasing the package name to a package variable that implements an interface that matches the packages functions. This has the advantage of not requiring inline changes where package "a" is used.

The idea revolves around creating an interface for the functions you need from the external package, a pass-through implementation of that interface for the default behavior and a mock implementation for testing. At the beginning of a test just replace the package variable with the mock.

b/a_interface.go

package b  import (     aa "a" // alias the "a" package )  // internal interface for `a` package functions `DoSomething()` type aDoer interface {     DoSomething() string }  // default implementation of the aDoer interface type aType struct{}  func (aType) DoSomething() string { // just pass-through to package "a"     return aa.DoSomething() } 

b/b.go - is unmodified other then removing the import:

package b  type B struct{}  func NewB() B {     return B{} }  func (b B) Results() string{     return a.DoSomething() // <- uses aType{} implementation by default } 

b/b_test.go

package b  import (     "testing"      "github.com/stretchr/testify/assert" )  // mock implementation of aDoer interface type aMock struct{}  func (aMock) DoSomething() string {     return "complete" }  func TestResults(t *testing.T) {     a = aMock{}  // <- replace the default with the mock      b := NewB()      result = b.Results()     assert.Equal(t, "complete", result)             } 

It's a bit on the sneaky side, so you'll probably want to make clear comments about what's going on.


If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment