DEV Community

loading...

Ensure auto-generated code is always up-to-date with compile-time assertions in Go

arashout profile image Arash Outadi Updated on ・2 min read

Introduction

Do you get annoyed when others (or you) forget to run the code generation command and now nothing works/code is out-dated?

Compile Time Assertions to the rescue!

Scenario

Suppose you have an interface:

type MyInterface interface {
    WithMethod(method string) MyInterface
}

This interface has an associated implementation:

type MyInterfaceImpl struct {
    privateField string
}
func (my *MyInterfaceImpl) MyInterface {
    // ... implementation code
}

Which you've have been auto-generating mock objects for using the awesome mockery package.

Why Though

You are doing this because you want users of your library to be able to mock your library.

  • Thus allowing them to isolate their own code when writing unit tests.
  • Meaning we don't actually use this mock in OUR tests, it is used by OTHERS

So the interface definition above is augmented with:

// Generate mocks by running "go generate ./..."
// go:generate mockery --name MyInteface
type MyInterface interface {
    WithMethod(method string) MyInterface
}

Which produces when go generate ./... is ran:
mocks/MyInterface.go

// MyInterface is an autogenerated mock type for the MyInterface type
type MyInterface struct {
    mock.Mock
}

// WithMethod provides a mock function with given fields: method
func (_m *MyInterface) WithMethod(method string) (pkg.MyInterface) {
// ... some mock implementation code

Problem

Now what happens when Greg who isn't aware of the auto-generated code, adds another method to MyInterface and MyInterfaceImpl:

type MyInterface interface {
    WithMethod(method string) MyInterface
    WithAnotherMethod(method string) MyInterface // New
}

Since the MyInterface has changed but Greg didn't auto-generate the mocks using go generate ./..., the mock object will not fulfill the interface and cannot be used for testing by downstream users (Thanks Greg)!

Solution

Add a compile-assertion above the interface definition after you have initially generated the mock object (Also add --inpackage flag to go generate command to avoid import cycles).

// Generate mocks by running "go generate ./..."
// go:generate mockery --name MyInteface --inpackage
var _ MyInterface = &MockMyInterface{} // COMPILE-TIME Assertion
type MyInterface interface {
    WithMethod(method string) MyInterface
}

Now if Greg adds another method but forgets to run go generate

var _ MyInterface = &MockMyInterface{}
type MyInterface interface {
    WithMethod(method string) MyInterface
    WithAnotherMethod(method string) MyInterface // New
}

He will get hit with:

cannot use &MockMyInterface literal (type *MockMyInterface) as type MyInterface in assignment:
    *MockMyInterface does not implement MyInterface (missing WithAnotherMethod method)

At which point, he might realize that he needs to add the method to the mock object using go generate.

Caveat: You will need to comment out the compile-time assertion var _ MyInterface = &MockMyInterface{} when running go generate/mockery tool

Discussion (0)

pic
Editor guide