DEV Community

Arash Outadi
Arash Outadi

Posted on • Updated on

 

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

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

Top comments (0)