github.com/joomcode/pegomock@v2.9.2-0.20220414140958-14f53b6b2a6c+incompatible/README.md (about) 1 PegoMock is a mocking framework for the [Go programming language](http://golang.org/). It integrates well with Go's built-in `testing` package, but can be used in other contexts too. It is based on [golang/mock](https://github.com/golang/mock), but uses a DSL closely related to [Mockito](http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html). 2 3 Joom-specific 4 === 5 This is a fork of [PegoMock framework](https://github.com/petergtz/pegomock). The reasoning behind the choice of pegomock as our _Go_-to mocking framework can be found [here](https://www.notion.so/joomteam/Go-Mock-reference-01eb736c0e98489c8429d11f2a87dd58#a51414c3a20d4fd1a80426f766f133cd). 6 7 ### Using pegomock, short version 8 1) [install](#getting-pegomock) pegomock 9 2) generate mocks: 10 * add a comment starting with `//go:generate pegomock generate $GOFILE` to the file containing the interfaces to mock ([example](https://github.com/joomcode/api/blob/ac3b048318cf2f0705ef3e3131c8b212738d5e4d/src/joom/app/report/visamarketplaceprogram/manager.go#L3)) 11 * to use type-aware matching for mocked method arguments, generate matcher functions by adding `-m` parameter to pegomock call. The helper functions will be created in a separate package (`mock/matchers`). 12 * if you'd like to see the generated code, either call the generator manually (`pegomock generate...`) or use `gauguin.sh` script to regenerate all files in the API repository. 13 3) use mocks in your test: 14 ```go 15 package test 16 17 func TestWithMock(t *testing.T) { 18 // mock initialization, uses WithT to handle test errors 19 myMock := mock.NewMock(pegomock.WithT(t)) 20 21 // now, set up the responses that mock's methods should return, based on the arguments passed 22 // simplest case, exact equality: mock.DoSomething(expectedArg1, expectedArg2) will return (1, "abc", nil) from now on 23 pegomock.When(myMock.DoSomething(expectedArg1, expectedArg2)).ThenReturn(1, "abc", nil) 24 // a conditional response, using matchers: when second param !=1 -> return (0, "", errorx.New("uh-oh") 25 pegomock.When(myMock.DoSomething(pegomock.AnyString(), pegomock.NotEqUint64(1))).ThenReturn(0, "", errorx.New("uh-oh")) 26 27 // here goes the test itself... 28 29 // verify that the interaction with the mocked object went as expected 30 mock.VerifyWasCalled(pegomock.Times(2)).DoSomething(pegomock.AnyString(), pegomock.AnyUint64()) 31 } 32 ``` 33 34 ### Known issues 35 36 #### Locally defined types 37 38 If the interface uses locally defined types, its mock has to be generated in the same package as the original interface. To generate such a mock, you need to include `--package=<original_package>` in the pegomock call. (This limitation is on our list of things to fix.) 39 40 Otherwise, please use the recommended way to create mocks: 41 42 `--output=<package_name>_mock/<source_filename>_gen.go --package=<package_name>_mock` 43 44 This creates them in a separate package names `<package_name>_mock` 45 46 #### Duplicate matchers generated with Bazel 47 48 When running new mock-using tests via Bazel, you may encounter an error that looks like this: 49 ``` 50 bazel-out/.../paycore_impl_mocks/matchers_refund_account_dao_mock.go/context_context.go:11:6: AnyContextContext redeclared in this block 51 bazel-out/.../matchers_cancel_account_dao_mock.go/context_context.go:11:26: previous declaration 52 ``` 53 54 When pegomock generates several mocks and their matchers for a single mock package, the calls to generator are being made in parallel, so any generator instance isn't aware of its neighbouring generators. 55 56 Pegomock believes it should generate comparator functions for every single type it encountered in the source file. When there's an intersection between the used types in different mocks (e.g., `context.Context` is used in almost any interface), collisions arise due to the same comparators being generated twice or more and put into the same package (`mock_matchers`). 57 58 The currently used workaround is to explicitly specify which types should not have their matchers generated in a pegomock call (there's a flag `skip-matchers`, which allows to do exactly that). 59 60 To fix the case above, add `--skip-matchers=context_context` to one of the pegomock calls so that `context.Context` matchers are generated only once. 61 62 ### Differences from the master branch: 63 * `--skip-changes` flag allows to avoid generating duplicate matchers inside mock package 64 65 --- 66 #### The original documentation goes below. 67 68 [![Build Status](https://travis-ci.org/petergtz/pegomock.svg?branch=master)](https://travis-ci.org/petergtz/pegomock) 69 70 Getting Pegomock 71 ================ 72 ``` 73 go install github.com/joomcode/pegomock/pegomock@latest 74 ``` 75 76 This will download the package and install an executable `pegomock` in your `$GOPATH/bin`. 77 78 See also section [Tracking the pegomock tool in your project](#tracking-the-pegomock-tool-in-your-project) for a per-project control of the tool version. 79 80 Getting Started 81 =============== 82 83 Using Pegomock with Golang’s XUnit-style Tests 84 ---------------------------------------------- 85 86 The preferred way is: 87 88 ```go 89 import ( 90 "github.com/petergtz/pegomock" 91 "testing" 92 ) 93 94 func TestUsingMocks(t *testing.T) { 95 mock := NewMockPhoneBook(pegomock.WithT(t)) 96 97 // use your mock here 98 } 99 ``` 100 101 102 Alternatively, you can set a global fail handler within your test: 103 104 ```go 105 func TestUsingMocks(t *testing.T) { 106 pegomock.RegisterMockTestingT(t) 107 108 mock := NewMockPhoneBook() 109 110 // use your mock here 111 } 112 ``` 113 **Note:** In this case, Pegomock uses a global (singleton) fail handler. This has the benefit that you don’t need to pass the fail handler down to each test, but does mean that you cannot run your XUnit style tests in parallel with Pegomock. 114 115 If you configure both a global fail handler and a specific one for your mock, the specific one overrides the global fail handler. 116 117 Using Pegomock with Ginkgo 118 -------------------------- 119 120 When a Pegomock verification fails, it calls a `FailHandler`. This is a function that you must provide using `pegomock.RegisterMockFailHandler()`. 121 122 If you’re using [Ginkgo](http://onsi.github.io/ginkgo/), all you need to do is: 123 124 ```go 125 pegomock.RegisterMockFailHandler(ginkgo.Fail) 126 ``` 127 128 before you start your test suite. 129 130 ### Avoiding Ginkgo Naming Collision with `When` Function 131 132 Ginkgo introduced a new keyword in its DSL: `When`. This causes name collisions when dot-importing both Ginkgo and Pegomock. To avoid this, you can use a different dot-import for Pegomock which uses `Whenever` instead of `When`. Example: 133 134 ```go 135 package some_test 136 137 import ( 138 . "github.com/onsi/ginkgo" 139 . "github.com/petergtz/pegomock/ginkgo_compatible" 140 ) 141 142 var _ = Describe("Some function", func() { 143 When("certain condition", func() { 144 It("succeeds", func() { 145 mock := NewMockPhoneBook() 146 Whenever(mock.GetPhoneNumber(EqString("Tom"))).ThenReturn("123-456-789") 147 }) 148 }) 149 }) 150 ``` 151 152 Generating Your First Mock and Using It 153 --------------------------------------- 154 155 Let's assume you have: 156 157 ```go 158 type Display interface { 159 Show(text string) 160 } 161 ``` 162 163 The simplest way is to call `pegomock` from within your go package specifying the interface by its name: 164 165 ``` 166 cd path/to/package 167 pegomock generate Display 168 ``` 169 170 This will generate a `mock_display_test.go` file which you can now use in your tests: 171 172 ```go 173 // creating mock 174 display := NewMockDisplay() 175 176 // using the mock 177 display.Show("Hello World!") 178 179 // verifying 180 display.VerifyWasCalledOnce().Show("Hello World!") 181 ``` 182 183 Why yet Another Mocking Framework for Go? 184 ========================================= 185 186 I've looked at some of the other frameworks, but found none of them satisfying: 187 - [GoMock](https://github.com/golang/mock) seemed overly complicated when setting up mocks and verifying them. The command line interface is also not quite intuitive. That said, Pegomock is based on the GoMock, reusing mostly the mockgen code. 188 - [Counterfeiter](https://github.com/maxbrunsfeld/counterfeiter) uses a DSL that I didn't find expressive enough. It often seems to need more lines of code too. In one of its samples, it uses e.g.: 189 190 ```go 191 fake.DoThings("stuff", 5) 192 Expect(fake.DoThingsCallCount()).To(Equal(1)) 193 194 str, num := fake.DoThingsArgsForCall(0) 195 Expect(str).To(Equal("stuff")) 196 Expect(num).To(Equal(uint64(5))) 197 ``` 198 199 In Pegomock, this can be written as simple as: 200 201 ```go 202 fake.DoThings("stuff", 5) 203 fake.VerifyWasCalledOnce().DoThings("stuff", 5) 204 ``` 205 - [Hel](https://github.com/nelsam/hel) uses a new and interesting approach to setting up and verifying mocks. However, I wonder how flexible it actually is. E.g. how about providing a callback function when stubbing? Can this be modeled with its current approach using channels? 206 207 In addition, Pegomock provides a "watch" command similar to [Ginkgo](http://onsi.github.io/ginkgo/), which constantly watches over changes in an interface and updates its mocks. It gives the framework a much more dynamic feel, similar to mocking frameworks in Ruby or Java. 208 209 Using Mocks In Your Tests 210 ========================= 211 212 Verifying Behavior 213 ------------------ 214 215 Interface: 216 217 ```go 218 type Display interface { 219 Show(text string) 220 } 221 ``` 222 223 Test: 224 225 ```go 226 // creating mock: 227 display := NewMockDisplay() 228 229 // using the mock: 230 display.Show("Hello World!") 231 232 // verifying: 233 display.VerifyWasCalledOnce().Show("Hello World!") 234 ``` 235 236 Stubbing 237 -------- 238 239 Interface: 240 241 ```go 242 type PhoneBook interface { 243 GetPhoneNumber(name string) string 244 } 245 ``` 246 247 Test: 248 249 ```go 250 // creating the mock 251 phoneBook := NewMockPhoneBook() 252 253 // stubbing: 254 When(phoneBook.GetPhoneNumber("Tom")).ThenReturn("345-123-789") 255 When(phoneBook.GetPhoneNumber("Invalid")).ThenPanic("Invalid Name") 256 257 // prints "345-123-789": 258 fmt.Println(phoneBook.GetPhoneNumber("Tom")) 259 260 // panics: 261 fmt.Println(phoneBook.GetPhoneNumber("Invalid")) 262 263 // prints "", because GetPhoneNumber("Dan") was not stubbed 264 fmt.Println(phoneBook.GetPhoneNumber("Dan")) 265 266 // Although it is possible to verify a stubbed invocation, usually it's redundant 267 // If your code cares what GetPhoneNumber("Tom") returns, then something else breaks (often even before a verification gets executed). 268 // If your code doesn't care what GetPhoneNumber("Tom") returns, then it should not be stubbed. 269 270 // Not convinced? See http://monkeyisland.pl/2008/04/26/asking-and-telling. 271 phoneBook.VerifyWasCalledOnce().GetPhoneNumber("Tom") 272 ``` 273 274 - By default, for all methods that return a value, a mock will return zero values. 275 - Once stubbed, the method will always return a stubbed value, regardless of how many times it is called. 276 - `ThenReturn` supports chaining, i.e. `ThenReturn(...).ThenReturn(...)` etc. The mock will return the values in the same order the chaining was done. The values from the last `ThenReturn` will be returned indefinitely when the number of call exceeds the `ThenReturn`s. 277 278 Stubbing Functions That Have no Return Value 279 -------------------------------------------- 280 281 Stubbing functions that have no return value requires a slightly different approach, because such functions cannot be passed directly to another function. However, we can wrap them in an anonymous function: 282 283 ```go 284 // creating mock: 285 display := NewMockDisplay() 286 287 // stubbing 288 When(func() { display.Show("Hello World!") }).ThenPanic("Panicking") 289 290 // panics: 291 display.Show("Hello World!") 292 ``` 293 294 Argument Matchers 295 ----------------- 296 297 Pegomock provides matchers for stubbing and verification. 298 299 Verification: 300 301 ```go 302 display := NewMockDisplay() 303 304 // Calling mock 305 display.Show("Hello again!") 306 307 // Verification: 308 display.VerifyWasCalledOnce().Show(AnyString()) 309 ``` 310 311 Stubbing: 312 313 ```go 314 phoneBook := NewMockPhoneBook() 315 316 // Stubbing: 317 When(phoneBook.GetPhoneNumber(AnyString())).ThenReturn("123-456-789") 318 319 // Prints "123-456-789": 320 fmt.Println(phoneBook.GetPhoneNumber("Dan")) 321 // Also prints "123-456-789": 322 fmt.Println(phoneBook.GetPhoneNumber("Tom")) 323 ``` 324 325 **Important**: When you use argument matchers, you must always use them for all arguments: 326 327 ```go 328 // Incorrect, panics: 329 When(contactList.getContactByFullName("Dan", AnyString())).thenReturn(Contact{...}) 330 // Correct: 331 When(contactList.getContactByFullName(EqString("Dan"), AnyString())).thenReturn(Contact{...}) 332 ``` 333 334 ### Writing Your Own Argument Matchers 335 336 **Important:** `Eq...`, `NotEq...`, `Any...` and `...That` matchers for types used in mock methods, 337 can now be _auto-generated_ while generating the mock. The `...That` argument matcher accepts an 338 argument implementing the `pegomock.ArgumentMatcher` interface and allows you to write and use custom 339 matcher logic without having to create a new argument matcher method for each type you want to match. 340 341 So writing your own argument matchers is not necessary for most use cases. See section 342 [The Pegomock CLI](#generating-mocks) for more information. 343 344 If you are not using the option to generate matchers you can write your own for non-basic types. 345 E.g. if you have a `struct MyType`, you can write an _Equals_ and _Any_ matcher like this: 346 ```go 347 func EqMyType(value MyType) MyType { 348 RegisterMatcher(&EqMatcher{Value: value}) 349 return MyType{} 350 } 351 352 func AnyMyType() MyType { 353 RegisterMatcher(NewAnyMatcher(reflect.TypeOf(MyType{}))) 354 return MyType{} 355 } 356 ``` 357 358 359 Verifying the Number of Invocations 360 ----------------------------------- 361 362 ```go 363 display := NewMockDisplay() 364 365 // Calling mock 366 display.Show("Hello") 367 display.Show("Hello, again") 368 display.Show("And again") 369 370 // Verification: 371 display.VerifyWasCalled(Times(3)).Show(AnyString()) 372 // or: 373 display.VerifyWasCalled(AtLeast(3)).Show(AnyString()) 374 // or: 375 display.VerifyWasCalled(Never()).Show("This one was never called") 376 ``` 377 378 Verifying in Order 379 ------------------ 380 381 ```go 382 display1 := NewMockDisplay() 383 display2 := NewMockDisplay() 384 385 // Calling mocks 386 display1.Show("One") 387 display1.Show("Two") 388 display2.Show("Another two") 389 display1.Show("Three") 390 391 // Verification: 392 inOrderContext := new(InOrderContext) 393 display1.VerifyWasCalledInOrder(Once(), inOrderContext).Show("One") 394 display2.VerifyWasCalledInOrder(Once(), inOrderContext).Show("Another two") 395 display1.VerifyWasCalledInOrder(Once(), inOrderContext).Show("Three") 396 ``` 397 398 Note that it's not necessary to verify the call for `display.Show("Two")` if that one is not of any interested. An `InOrderContext` only verifies that the verifications that are done, are in order. 399 400 Stubbing with Callbacks 401 ------------------------ 402 403 ```go 404 phoneBook := NewMockPhoneBook() 405 406 // Stubbing: 407 When(phoneBook.GetPhoneNumber(AnyString())).Then(func(params []Param) ReturnValues { 408 return []ReturnValue{fmt.Sprintf("1-800-CALL-%v", strings.ToUpper(params[0]))} 409 }, 410 411 412 // Prints "1-800-CALL-DAN": 413 fmt.Println(phoneBook.GetPhoneNumber("Dan")) 414 // Prints "1-800-CALL-TOM": 415 fmt.Println(phoneBook.GetPhoneNumber("Tom")) 416 ``` 417 418 419 Verifying with Argument Capture 420 -------------------------------- 421 422 In some cases it can be useful to capture the arguments from mock invocations and assert on them separately. This method is only recommended if the techniques using matchers are not sufficient. 423 424 ```go 425 display := NewMockDisplay() 426 427 // Calling mock 428 display.Show("Hello") 429 display.Show("Hello, again") 430 display.Show("And again") 431 432 // Verification and getting captured arguments 433 text := display.VerifyWasCalled(AtLeast(1)).Show(AnyString()).GetCapturedArguments() 434 435 // Captured arguments are from last invocation 436 Expect(text).To(Equal("And again")) 437 ``` 438 439 You can also get all captured arguments: 440 441 ```go 442 // Verification and getting all captured arguments 443 texts := display.VerifyWasCalled(AtLeast(1)).Show(AnyString()).GetAllCapturedArguments() 444 445 // Captured arguments are a slice 446 Expect(texts).To(ConsistOf("Hello", "Hello, again", "And again")) 447 ``` 448 449 Verifying with Asynchronous Mock Invocations 450 -------------------------------------------- 451 452 When the code exercising the mock is run as part of a Goroutine, it's necessary to verify in a polling fashion until a timeout kicks in. `VerifyWasCalledEventually` can help here: 453 ```go 454 display := NewMockDisplay() 455 456 go func() { 457 doSomething() 458 display.Show("Hello") 459 }() 460 461 display.VerifyWasCalledEventually(Once(), 2*time.Second).Show("Hello") 462 ``` 463 464 465 The Pegomock CLI 466 ================ 467 468 Installation 469 ------------ 470 471 Install it via: 472 473 ``` 474 go install github.com/petergtz/pegomock/pegomock 475 ``` 476 477 Tracking the pegomock tool in your project 478 ------------------------------------------ 479 480 Go modules allow to pin not only a package but also a tool (that is, an executable). The steps are: 481 482 1. Use a file named `tools.go` with contents similar to this: 483 ```go 484 // +build tools 485 486 // This file will never be compiled (see the build constraint above); it is 487 // used to record dependencies on build tools with the Go modules machinery. 488 // See https://github.com/go-modules-by-example/index/blob/master/010_tools/README.md 489 490 package tools 491 492 import ( 493 _ "github.com/petergtz/pegomock/pegomock" 494 ) 495 ``` 496 2. Set `$GOBIN` to a `bin` directory relative to your repo (this defines where tool dependencies will be installed). 497 2. Install the tool with `go install`: 498 ```console 499 $ cd /path/to/myproject 500 $ export GOBIN=$PWD/bin 501 $ go install github.com/petergtz/pegomock/pegomock 502 ``` 503 3. Use that `$GOBIN` when invoking `pegomock` for that project: 504 ```console 505 $ $GOBIN/pegomock ... 506 ``` 507 or 508 ```console 509 $ export PATH=$GOBIN:$PATH 510 $ pegomock ... 511 ``` 512 513 See [Tools as dependencies] for details. 514 515 [Tools as dependencies]: https://github.com/go-modules-by-example/index/blob/master/010_tools/README.md 516 517 Generating Mocks 518 ---------------- 519 520 Pegomock can generate mocks in two different ways: 521 522 1. by parsing source code Go files 523 524 ``` 525 pegomock generate [<flags>] <gofile> 526 ``` 527 528 2. by building a Go package and using reflection 529 530 ``` 531 pegomock generate [<flags>] [<packagepath>] <interfacename> 532 ``` 533 534 Flags can be any of the following: 535 536 - `--output,-o`: Output file; defaults to mock_<interface>_test.go. 537 538 - `--package`: Package of the generated code; defaults to the package from which pegomock was executed suffixed with _test 539 540 - `--generate-matchers,-m`: This will auto-generate argument matchers and place them in a `matchers` directory alongside the mock source code itself. 541 542 For more flags, run: 543 544 ``` 545 pegomock --help 546 ``` 547 548 Generating Mocks with `--use-experimental-model-gen` 549 ---------------------------------------------------- 550 551 There are a number of shortcomings in the current reflection-based implementation. 552 To overcome these, there is now an option to use a new, experimental implementation that is based on [golang.org/x/tools/go/loader](https://godoc.org/golang.org/x/tools/go/loader). 553 To use it when generating your mocks, invoke `pegomock` like this: 554 555 ``` 556 pegomock generate --use-experimental-model-gen [<flags>] [<packagepath>] <interfacename> 557 ``` 558 559 What are the benefits? 560 - The current default uses the [reflect](https://golang.org/pkg/reflect/) package to introspect the interface for which a mock should be generated. But reflection cannot determine method parameter names, only types. This forces the generator to generate them based on a pattern. In a code editor with code assistence, those pattern-based names (such as `_param0`, `_param1`) are non-descriptive and provide less help while writing code. The new implementation properly parses the source (including *all* dependent packages) and subsequently uses the same names as used in the interface definition. 561 - With the current default you cannot generate an interface that lives in the `main` package. It's due to the way this implementation works: it imports the interface's package into temporarily generated code that gets compiled on the fly. This compilation fails, because there are now two `main` functions. 562 - The new implementation is simpler and will probably become the default in the future, because it will be easier to maintain. 563 564 What are the drawbacks? 565 - There is only one drawback: maturity. The new implementation is not complete yet, and also might have some bugs that still need to be fixed. 566 567 Users of Pegomock are encouraged to use this new option and report any problems by [opening an issue](https://github.com/petergtz/pegomock/issues/new). Help to stabilize it is greatly appreciated. 568 569 Generating mocks with `go generate` 570 ---------------------------------- 571 572 `pegomock` can be used with `go generate`. Simply add the directive to your source file. 573 574 Here's an example for a Display interface used by a calculator program: 575 576 ```go 577 // package/path/to/display/display.go 578 579 package display 580 581 type Display interface { 582 Show(text string) 583 } 584 ``` 585 586 ```go 587 // package/path/to/calculator/calculator_test.go 588 589 package calculator_test 590 591 //go:generate pegomock generate package/path/to/display Display 592 593 // Use generated mock 594 mockDisplay := NewMockDisplay() 595 ... 596 ``` 597 598 Generating it: 599 ```sh 600 cd package/path/to/calculator 601 go generate 602 ``` 603 604 **Note:** While you could add the directive adjacent to the interface definition, the author's opinion is that this violates clean dependency management and would pollute the package of the interface. 605 It's better to generate the mock in the same package, where it is used (if this coincides with the interface package, that's fine). That way, not only stays the interface's package clean, the tests also don't need to prefix the mock with a package, or use a dot-import. 606 607 Continuously Generating Mocks 608 ----------------------------- 609 610 The `watch` command lets Pegomock generate mocks continuously on every change to an interface: 611 612 ``` 613 pegomock watch 614 ``` 615 616 For this, Pegomock expects an `interfaces_to_mock` file in the package directory where the mocks should be generated. In fact, `pegomock watch` will create it for you if it doesn't exist yet. The contents of the file are similar to the ones of the `generate` command: 617 618 ``` 619 # Any line starting with a # is treated as comment. 620 621 # interface name without package specifies an Interface in the current package: 622 PhoneBook 623 624 # generates a mock for SomeInterfacetaken from mypackage: 625 path/to/my/mypackage SomeInterface 626 627 # you can also specify a Go file: 628 display.go 629 630 # and use most of the flags from the "generate" command 631 --output my_special_output.go MyInterface 632 ``` 633 634 Flags can be: 635 636 - `--recursive,-r`: Recursively watch sub-directories as well. 637 638 Removing Generated Mocks 639 ----------------------------- 640 641 Sometimes it can be useful to systematically remove all mocks and matcher files generated by Pegomock. For this purpose, there is the `remove` command. By simply calling it from the current directory 642 ``` 643 pegomock remove 644 ``` 645 it will remove all Pegomock-generated files in the current directory. It supports additional flags, such as `--recursive` to recursively remove all Pegomock-generated files in sub-directories as well. To see all possible options, run: 646 ``` 647 pegomock remove --help 648 ```