Skip to content

Instantly share code, notes, and snippets.

@maratori
Last active June 7, 2024 18:26
Show Gist options
  • Save maratori/8772fe158ff705ca543a0620863977c2 to your computer and use it in GitHub Desktop.
Save maratori/8772fe158ff705ca543a0620863977c2 to your computer and use it in GitHub Desktop.
Comparison of golang mocking libraries

Comparison of golang mocking libraries

Updated 2024-05-29

Uber
gomock
testify + mockery minimock moq Google
gomock

Library

GitHub stars s6 s2 + s3 s4 s5 s1
Latest release date d6 d2 + d3 d4 d5 d1
Maintained

Mock creation

Code generation 1
Not only code generation 2
Use t.Cleanup() 3
Support generics

DSL

Execute custom func 4
Typed expected arguments 5 ✖️ ✖️ ✔️ ✔️ ✖️
Typed returned values 6 ✔️ ✔️ ✔️ ✔️ ✖️
Calls order 7
Wait for time.Duration 8
Wait for message from chan 9
Panic 10
Assert expectations with timeout
Return zero values by default 11

Calls count

Exact calls count 12
Min/max calls count 13
Called at least once
Don't check calls count
Expect once by default 14

Matchers

Any value
Equal to
Regex
Not equal to
Is nil
Type is
Length is
Slice elements in any order
Compose matchers with logical or
Custom matcher

Examples

Uber gomock

func TestUberGomock(t *testing.T) {
	ctrl := gomock.NewController(t)
	m := NewMockMyInterface(ctrl)
	gomock.InOrder(
		m.EXPECT().Method(gomock.Any(), "abc").Return(123, nil),
		m.EXPECT().AnotherMethod(gomock.Any(), gomock.Len(3)),
	)
	...
}

testify/mock + mockery

func TestTestifyMock(t *testing.T) {
	m := mocks.NewMyInterface(t)
	m.EXPECT().Method(mock.Anything, "abc").After(5*time.Second).Return(123, nil).Once()
	m.EXPECT().AnotherMethod(mock.Anything, "abc").Return(0, nil).Once()
	...
}

minimock

func TestMinimock(t *testing.T) {
	ctrl := minimock.NewController(t)
	m := NewMyInterfaceMock(ctrl)
	m.MethodMock.When(minimock.AnyContext, "abc").Then(123, nil)
	m.AnotherMethodMock.When(minimock.AnyContext, "abc").Then(0, nil)
	...
}

moq

func TestMoq(t *testing.T) {
	m := MyInterfaceMock{
		MethodFunc: func(ctx context.Context, s string) (int, error) {
			assert.Equal(t, "abc", s)
			return 123, nil
		},
		AnotherMethodFunc: func(ctx context.Context, s string) (int, error) {
			assert.Len(t, s, 1)
			return 0, nil
		},
	}
	t.Cleanup(func() {
		assert.Len(t, m.MethodCalls(), 1)
		assert.Len(t, m.AnotherMethodCalls(), 1)
	})
	...
}

Google gomock

func TestGoogleGomock(t *testing.T) {
	ctrl := gomock.NewController(t)
	m := NewMockMyInterface(ctrl)
	gomock.InOrder(
		m.EXPECT().Method(gomock.Any(), "abc").Return(123, nil),
		m.EXPECT().AnotherMethod(gomock.Any(), gomock.Len(3)),
	)
	...
}

Footnotes

  1. CLI tool to auto generate mocks from interfaces

  2. Allow writing mock manually, without code generation

  3. Mock constructor uses t.Cleanup() to assert expectations after test by default

  4. Use arbitrary function to execute, allowing to implement any feature in the table

  5. Defining expected arguments in test doesn't rely on any (aka empty interface{})

  6. Defining returned values (with methods like Return and DoAndReturn) in test doesn't rely on any (aka empty interface{})

  7. Define expected order of calls

  8. Block execution of method using time.Sleep()

  9. Block execution of method using <- channel

  10. Panic instead of method execution

  11. Not defining return values leads to returning zero values

  12. Define expected exact number of calls in test

  13. Define expected min/max number of calls in test

  14. Not defining number of calls leads to expectation that method to be called once

@arshamalh
Copy link

Now gomock is not maintained anymore and we can use its forked version by Uber instead, which might have different features than the one mentioned in this table.

@maratori
Copy link
Author

@arshamalh thanks for pointing this out. I need some time to check it and update the table.

@maratori
Copy link
Author

@arshamalh I've added Uber's gomock to the table

@Zurvarian
Copy link

Hi, thanks for this table, it is quite helpful.

There is another, quite new, mocking library that I've been evaluating for our projects that is quite promising too, in case you want to consider it: https://github.com/ovechkin-dm/mockio

It is still not well known, but what I've tested so far works fine and the fact that it does not require mock generation is quite helpful.

@unmarshall
Copy link

testify column needs correction in the matchers section. Using assert you can check nil (assert.Nil and assert.NotNil), similarly assert.Len to check the length, assert.NotEqual. For regular expressions (https://pkg.go.dev/github.com/pgpst/pgpst/internal/github.com/stretchr/testify/assert#Regexp). The comparison is quite nice and provides one pager comparison but needs to be updated so that one can rely upon it.

@maratori
Copy link
Author

maratori commented Jan 8, 2024

@unmarshall correct me if I'm wrong, but you can't use functions from the assert package as matchers for the mock package.

@maratori
Copy link
Author

maratori commented Jan 8, 2024

@Zurvarian mockio looks really interesting, thanks! I need to think if it's mature enough to add it to the table.

@unmarshall
Copy link

@maratori Ah you are right.
There is another framework which has received attention: https://github.com/vektra/mockery

@maratori
Copy link
Author

maratori commented Jan 9, 2024

@unmarshall mockery is already on the table.

@LandonTClipp
Copy link

Maintainer of mockery here. The table lists mockery has not having Typed expected arguments (with the strict requirement here being that we don't use interface{}). There is a way to utilize strictly type-equivalent assertions through .RunAndReturn, see here: https://vektra.github.io/mockery/latest/features/#expecter-structs

Example:

requesterMock.EXPECT().
    Get(mock.Anything).
    RunAndReturn(func(path string) string { 
        fmt.Println(path, "was called")
        return "result for " + path
    })

I intend v3 to address the "various ways to assert the same expectation" issue, but just an FYI that mockery does in fact allow this particular pattern.

@maratori
Copy link
Author

@LandonTClipp, thanks for pointing me this. Most probably, the wording in the table needs to be better. Typed expected arguments means that a method to define expectation is typed. The RunAndReturn method doesn't define an expectation. In your example, the method Get does it, and it accepts any.

But I've removed the wrong checkmark in Uber gomock for that feature.

Actually, I realized that having Typed expected arguments means not having matches at all.

@LandonTClipp
Copy link

Theoretically, setting all elements to mock.Anything to the argument matcher, then using .RunAndReturn has the effect of a totally type-safe expectation. This is kind of the style of matryer/moq, just done in a different way.

It's for sure a hard thing to categorize correctly :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment