├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ └── docs.yml ├── .gitignore ├── .golangci.yml ├── .mockery.yml ├── LICENSE ├── Makefile ├── README.md ├── config └── config.go ├── docs ├── features │ ├── captors.md │ ├── code-generation.md │ ├── configuration.md │ ├── error-reporting.md │ ├── matchers.md │ ├── method-stubbing.md │ ├── parallel-execution.md │ └── verification.md ├── index.md ├── limitations.md ├── requirements.txt └── sponsors.md ├── go.mod ├── go.sum ├── matchers ├── capture.go ├── controller.go ├── matchers.go ├── reporter.go ├── returners.go └── verification.go ├── mkdocs.yml ├── mock └── api.go ├── mockopts └── options.go ├── registry ├── captor.go ├── handler.go ├── matchers.go ├── registry.go ├── reporter.go ├── returners.go ├── state.go └── util.go ├── templates ├── mockery.tmpl └── mockery.tmpl.schema.json ├── tests ├── captor │ └── captor_test.go ├── check │ └── check_test.go ├── codegen │ ├── codegen_test.go │ ├── ifaces.go │ └── mocks_test.go ├── common │ └── common.go ├── concurrent │ └── concurrent_test.go ├── match │ └── match_test.go ├── mocking │ └── mock_test.go ├── reporting │ └── error_reporting_test.go ├── returners │ └── returners_test.go ├── simple │ └── simple_test.go ├── variadic │ └── variadic_test.go ├── verify │ └── verify_test.go └── when │ ├── when_double_test.go │ ├── when_single_test.go │ └── when_test.go └── threadlocal └── threadlocal.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [ovechkin-dm] 2 | 3 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # Workflow file of GitHub Actions 2 | 3 | name: build 4 | 5 | on: 6 | push: 7 | branches: 8 | - main 9 | - feature/** 10 | pull_request: 11 | branches: 12 | - main 13 | 14 | jobs: 15 | Lint: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout scm 19 | uses: actions/checkout@v3 20 | 21 | - name: Set up Go 22 | uses: actions/setup-go@v4 23 | with: 24 | go-version-file: go.mod 25 | 26 | - name: Lint 27 | uses: golangci/golangci-lint-action@v7 28 | with: 29 | version: v2.0.2 30 | 31 | CodeQL: 32 | needs: Lint 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Checkout scm 36 | uses: actions/checkout@v3 37 | 38 | - name: Set up Go 39 | uses: actions/setup-go@v4 40 | with: 41 | go-version-file: go.mod 42 | 43 | - name: Initialize CodeQL 44 | uses: github/codeql-action/init@v2 45 | with: 46 | languages: go 47 | 48 | - name: Perform CodeQL Analysis 49 | uses: github/codeql-action/analyze@v2 50 | 51 | Test: 52 | needs: Lint 53 | runs-on: ${{ matrix.runs-on }} 54 | strategy: 55 | fail-fast: false 56 | matrix: 57 | os: [linux] 58 | arch: [ amd64, arm64 ] 59 | go: [ '1.20', '1.22', '1.23' ] 60 | include: 61 | - os: linux 62 | runs-on: ubuntu-22.04 63 | steps: 64 | - name: Checkout scm 65 | uses: actions/checkout@v3 66 | 67 | - name: Set up Go 68 | uses: actions/setup-go@v4 69 | with: 70 | go-version: ${{ matrix.go }} 71 | 72 | - name: 'Test on linux [amd64]' 73 | if: ${{ matrix.os == 'linux' && contains(fromJson('["amd64"]'), matrix.arch) }} 74 | env: 75 | GOOS: ${{ matrix.os }} 76 | GOARCH: ${{ matrix.arch }} 77 | run: go test -race -v -coverprofile="coverage.txt" -coverpkg=./... ./... 78 | 79 | - name: 'Setup qemu-user-static on [linux] arch [arm64]' 80 | if: ${{ matrix.os == 'linux' && contains(fromJson('["arm64"]'), matrix.arch) }} 81 | run: | 82 | sudo apt-get update 83 | sudo apt-get -y install qemu-user-static 84 | 85 | - name: 'Test on [linux] arch [arm64]' 86 | if: ${{ matrix.os == 'linux' && contains(fromJson('["arm64"]'), matrix.arch) }} 87 | env: 88 | GOOS: ${{ matrix.os }} 89 | GOARCH: ${{ matrix.arch }} 90 | run: go test -coverpkg=./... ./... 91 | 92 | - name: Codecov 93 | uses: codecov/codecov-action@v3 94 | with: 95 | token: ${{ secrets.CODECOV_TOKEN }} 96 | name: Codecov on ${{ matrix.os }}/${{ matrix.arch }} go${{ matrix.go }} 97 | fail_ci_if_error: false 98 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | on: 3 | push: 4 | tags: 5 | - 'v*.*.*' 6 | permissions: 7 | contents: write 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-python@v4 14 | with: 15 | python-version: 3.x 16 | - uses: actions/cache@v4 17 | with: 18 | key: ${{ github.ref }} 19 | path: .cache 20 | - run: sudo apt-get install -y libcairo2-dev libfreetype6-dev libffi-dev libjpeg-dev libpng-dev libz-dev 21 | - run: pip install -r docs/requirements.txt 22 | env: 23 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 24 | - name: Setup git 25 | run: | 26 | git config --global user.name mockio-actions 27 | git config --global user.email mockio-actions@ovechkin-dm.github.io 28 | git fetch origin gh-pages --depth=1 29 | - name: Deploy docs 30 | run: "mike deploy --push --update-aliases $(echo ${{ github.ref_name }} | cut -d'.' -f1-2 | xargs printf '%s.0' ) latest" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage.txt 2 | .cache 3 | .idea 4 | *.dylib -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | timeout: 10m 4 | linters: 5 | default: none 6 | enable: 7 | - asciicheck 8 | - bidichk 9 | - errcheck 10 | - govet 11 | - ineffassign 12 | - staticcheck 13 | - unused 14 | formatters: 15 | enable: 16 | - gci 17 | - gofumpt 18 | settings: 19 | gci: 20 | sections: 21 | - standard 22 | - default 23 | - prefix(github.com/ovechkin-dm/mockio) 24 | - blank 25 | - dot 26 | - alias 27 | -------------------------------------------------------------------------------- /.mockery.yml: -------------------------------------------------------------------------------- 1 | all: false 2 | dir: '{{.InterfaceDir}}' 3 | filename: mocks_test.go 4 | force-file-write: true 5 | formatter: goimports 6 | log-level: info 7 | structname: '{{.Mock}}{{.InterfaceName}}' 8 | pkgname: '{{.SrcPackageName}}' 9 | recursive: false 10 | require-template-schema-exists: true 11 | template: file://templates/mockery.tmpl 12 | template-schema: '{{.Template}}.schema.json' 13 | packages: 14 | github.com/ovechkin-dm/mockio/v2/tests/codegen: 15 | config: 16 | all: true 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 jet-black 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: gofumpt import lint 2 | 3 | init: 4 | go install mvdan.cc/gofumpt@v0.6.0 5 | go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.0.2 6 | go install github.com/daixiang0/gci@v0.12.3 7 | go install github.com/vektra/mockery/v3@v3.2.5 8 | 9 | lint: 10 | golangci-lint run ./... 11 | 12 | gofumpt: 13 | gofumpt -l -w . 14 | 15 | import: 16 | gci write --skip-generated -s standard -s default -s "prefix(github.com/ovechkin-dm/mockio)" -s blank -s dot -s alias . 17 | 18 | codegen: 19 | mockery 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mockio 2 | 3 | [![Build Status](https://github.com/ovechkin-dm/mockio/actions/workflows/build.yml/badge.svg)](https://github.com/ovechkin-dm/mockio/actions) 4 | [![Codecov](https://codecov.io/gh/ovechkin-dm/mockio/branch/main/graph/badge.svg)](https://app.codecov.io/gh/ovechkin-dm/mockio) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/ovechkin-dm/mockio)](https://goreportcard.com/report/github.com/ovechkin-dm/mockio) 6 | [![Documentation](https://pkg.go.dev/badge/github.com/ovechkin-dm/mockio.svg)](https://pkg.go.dev/github.com/ovechkin-dm/mockio) 7 | [![Release](https://img.shields.io/github/release/ovechkin-dm/mockio.svg)](https://github.com/ovechkin-dm/mockio/releases) 8 | [![License](https://img.shields.io/github/license/ovechkin-dm/mockio.svg)](https://github.com/ovechkin-dm/mockio/blob/main/LICENSE) 9 | 10 | # Mock library for golang without code generation 11 | Mockio is a Golang library that provides functionality for mocking and stubbing functions and methods in tests inspired by mockito. The library is designed to simplify the testing process by allowing developers to easily create test doubles for their code, which can then be used to simulate different scenarios. 12 | 13 | # Documentation 14 | 15 | Latest documentation is available [here](https://ovechkin-dm.github.io/mockio/latest/) 16 | 17 | # Quick start 18 | 19 | Install latest version of the library using go get command: 20 | 21 | ```bash 22 | go get -u github.com/ovechkin-dm/mockio/v2 23 | ``` 24 | 25 | Create a simple mock and test: 26 | ```go 27 | package main 28 | 29 | import ( 30 | . "github.com/ovechkin-dm/mockio/v2/mock" 31 | "testing" 32 | ) 33 | 34 | type Greeter interface { 35 | Greet(name string) string 36 | } 37 | 38 | func TestGreet(t *testing.T) { 39 | ctrl := NewMockController(t) 40 | m := Mock[Greeter](ctrl) 41 | WhenSingle(m.Greet("John")).ThenReturn("Hello, John!") 42 | if m.Greet("John") != "Hello, John!" { 43 | t.Fail() 44 | } 45 | } 46 | ``` -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Option func(*MockConfig) 4 | 5 | type MockConfig struct { 6 | PrintStackTrace bool 7 | StrictVerify bool 8 | } 9 | 10 | func NewConfig() *MockConfig { 11 | return &MockConfig{ 12 | PrintStackTrace: true, 13 | StrictVerify: false, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docs/features/captors.md: -------------------------------------------------------------------------------- 1 | # Argument Captors 2 | 3 | Argument captors are a powerful feature that allow you to capture the arguments passed to a method when it is 4 | called. This is useful when you want to verify that a method was called with specific arguments, but you don't know what 5 | those arguments will be ahead of time. 6 | 7 | ## Creating a Captor 8 | 9 | To create a captor, you simply call the `Captor` function with the type of the argument you want to capture: 10 | 11 | ```go 12 | c := Captor[string]() 13 | ``` 14 | 15 | ## Using a Captor 16 | 17 | To use a captor, you pass it as an argument to the `When` function. When the method is called, the captor will capture the 18 | argument and store it in the captor's value: 19 | 20 | ```go 21 | When(greeter.Greet(c.Capture())).ThenReturn("Hello, world!") 22 | ``` 23 | 24 | ## Retrieving the Captured Values 25 | 26 | Argument captor records an argument on each stub call. You can retrieve the captured values by calling the `Values` method 27 | 28 | ```go 29 | capturedValues := c.Values() 30 | ``` 31 | 32 | If you want to retrieve just the last captured value, you can call the `Last` method 33 | 34 | ```go 35 | capturedValue := c.Last() 36 | ``` 37 | 38 | ## Example usage 39 | 40 | In this example we will create a mock, and use an argument captor to capture the arguments passed to the `Greet` method: 41 | 42 | ```go 43 | package main 44 | 45 | import ( 46 | . "github.com/ovechkin-dm/mockio/v2/mock" 47 | "testing" 48 | ) 49 | 50 | type Greeter interface { 51 | Greet(name any) string 52 | } 53 | 54 | func TestSimple(t *testing.T) { 55 | ctrl := NewMockController(t) 56 | greeter := Mock[Greeter](ctrl) 57 | c := Captor[string]() 58 | When(greeter.Greet(c.Capture())).ThenReturn("Hello, world!") 59 | _ = greeter.Greet("John") 60 | _ = greeter.Greet("Jane") 61 | if c.Values()[0] != "John" { 62 | t.Error("Expected John, got", c.Values()[0]) 63 | } 64 | if c.Values()[1] != "Jane" { 65 | t.Error("Expected Jane, got", c.Values()[1]) 66 | } 67 | } 68 | ``` -------------------------------------------------------------------------------- /docs/features/code-generation.md: -------------------------------------------------------------------------------- 1 | # Code Generation 2 | 3 | While Mockio is designed to work without code generation, it also provides a code generation tool that can help with creating mock implementations for your interfaces. This can be useful in cases where you want to have more control over the mock implementation, or don't want to rely on an unsafe features mockio uses. 4 | 5 | ## Installation 6 | 7 | Since mockio relies on mockery generation tool, mockery should be installed. 8 | 9 | ```sh 10 | go install github.com/vektra/mockery/v3@v3.2.5 11 | ``` 12 | 13 | Please refer to official mockery [installation guide](https://vektra.github.io/mockery/latest/installation/) 14 | 15 | ## Configuration 16 | 17 | To configure mockery tool, provide a link to mockio tempate: 18 | 19 | ```yaml 20 | all: false 21 | dir: '{{.InterfaceDir}}' 22 | filename: mocks_test.go 23 | force-file-write: true 24 | formatter: goimports 25 | log-level: info 26 | structname: '{{.Mock}}{{.InterfaceName}}' 27 | pkgname: '{{.SrcPackageName}}' 28 | recursive: false 29 | require-template-schema-exists: true 30 | template: https://raw.githubusercontent.com/ovechkin-dm/mockio/refs/heads/main/templates/mockery.tmpl 31 | template-schema: '{{.Template}}.schema.json' 32 | packages: 33 | your-package-name: 34 | config: 35 | all: true 36 | ``` 37 | 38 | The main difference here is the `template` parameter. 39 | 40 | Please refer to official mockery [configuration guide](https://vektra.github.io/mockery/latest/configuration/) for more details. 41 | 42 | ## Code generation 43 | 44 | Now that mockery is installed and configured, we can run mockery command in project root: 45 | 46 | ```sh 47 | mockery 48 | ``` 49 | 50 | It will generate necessary mocks 51 | 52 | ## Usage 53 | 54 | To use the generated mock, you can create them via generated contructor call `New{YourMockName}`. 55 | Here is an example: 56 | 57 | ``` 58 | func TestSimple(t *testing.T) { 59 | ctrl := NewMockController(t) 60 | m := NewMockUserService(ctrl) 61 | } 62 | ``` 63 | 64 | Other than that, API stays the same as if runtime mocks were used. 65 | 66 | ## Full example 67 | 68 | You can check full example [here](https://github.com/ovechkin-dm/mockio-codegen-example) 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /docs/features/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | ## Using options 4 | 5 | MockIO library can be configured by providing options from `mockopts` package inside `NewMockController()` function like this: 6 | ```go 7 | package main 8 | 9 | import ( 10 | . "github.com/ovechkin-dm/mockio/v2/mock" 11 | "github.com/ovechkin-dm/mockio/mockopts" 12 | "testing" 13 | ) 14 | 15 | func TestSimple(t *testing.T) { 16 | ctrl := NewMockController(t, mockopts.WithoutStackTrace()) 17 | } 18 | 19 | ``` 20 | 21 | ## StrictVerify 22 | **StrictVerify** adds extra checks on each test teardown. 23 | It will fail the test if there are any unverified calls. 24 | It will also fail the test if there are any calls that were not expected. 25 | 26 | ### Unverified calls check 27 | 28 | Consider the following example: 29 | ```go 30 | package main 31 | 32 | import ( 33 | . "github.com/ovechkin-dm/mockio/v2/mock" 34 | "github.com/ovechkin-dm/mockio/mockopts" 35 | "testing" 36 | ) 37 | 38 | type Greeter interface { 39 | Greet(name string) string 40 | } 41 | 42 | func TestSimple(t *testing.T) { 43 | ctrl := NewMockController(t, mockopts.StrictVerify()) 44 | greeter := Mock[Greeter](ctrl) 45 | When(greeter.Greet("John")).ThenReturn("Hello, John!") 46 | } 47 | 48 | ``` 49 | In this case, the test will fail because the `Greet` method was not called with the expected argument. 50 | If we want this test to pass, we need to call greeter with the expected argument: 51 | ```go 52 | func TestSimple(t *testing.T) { 53 | ctrl := NewMockController(t, mockopts.StrictVerify()) 54 | greeter := Mock[Greeter](ctrl) 55 | When(greeter.Greet("John")).ThenReturn("Hello, John!") 56 | greeter.Greet("John") 57 | } 58 | ``` 59 | 60 | ### Unexpected calls check 61 | 62 | Consider the following example: 63 | 64 | ```go 65 | func TestSimple(t *testing.T) { 66 | ctrl := NewMockController(t, mockopts.StrictVerify()) 67 | greeter := Mock[Greeter](ctrl) 68 | When(greeter.Greet("John")).ThenReturn("Hello, John!") 69 | greeter.Greet("John") 70 | greeter.Greet("Jane") 71 | } 72 | ``` 73 | 74 | In this case, the test will fail because the `Greet` method was called with an unexpected argument. 75 | If we want this test to pass, we need to remove the unexpected call, or add an expectation for it: 76 | ```go 77 | func TestSimple(t *testing.T) { 78 | ctrl := NewMockController(t, mockopts.StrictVerify()) 79 | greeter := Mock[Greeter](ctrl) 80 | When(greeter.Greet("John")).ThenReturn("Hello, John!") 81 | When(greeter.Greet("Jane")).ThenReturn("Hello, Jane!") 82 | greeter.Greet("John") 83 | greeter.Greet("Jane") 84 | } 85 | ``` 86 | 87 | ## WithoutStackTrace 88 | **WithoutStackTrace** option disables stack trace printing in case of test failure. 89 | 90 | Consider the following example: 91 | ```go 92 | package main 93 | 94 | import ( 95 | . "github.com/ovechkin-dm/mockio/v2/mock" 96 | "testing" 97 | ) 98 | 99 | type Greeter interface { 100 | Greet(name string) string 101 | } 102 | 103 | func TestSimple(t *testing.T) { 104 | ctrl := NewMockController(t, mockopts.StrictVerify()) 105 | greeter := Mock[Greeter](ctrl) 106 | When(greeter.Greet("Jane")).ThenReturn("hello world") 107 | greeter.Greet("John") 108 | VerifyNoMoreInteractions(greeter) 109 | } 110 | 111 | ``` 112 | 113 | If we run this test, we will see the following error: 114 | ``` 115 | === RUN TestSimple 116 | reporter.go:75: At: 117 | go/pkg/mod/github.com/ovechkin-dm/mockio@v0.7.2/registry/registry.go:130 +0x45 118 | Cause: 119 | No more interactions expected, but unverified interactions found: 120 | Greeter.Greet(John) at demo/hello_test.go:16 +0xf2 121 | Trace: 122 | demo.TestSimple.VerifyNoMoreInteractions.VerifyNoMoreInteractions.func1() 123 | go/pkg/mod/github.com/ovechkin-dm/mockio@v0.7.2/registry/registry.go:130 +0x45 124 | demo.TestSimple(0xc00018c4e0?) 125 | demo/hello_test.go:17 +0x15a 126 | testing.tRunner(0xc00018c4e0, 0x647ca0) 127 | /usr/local/go/src/testing/testing.go:1689 +0xfb 128 | created by testing.(*T).Run in goroutine 1 129 | /usr/local/go/src/testing/testing.go:1742 +0x390 130 | 131 | --- FAIL: TestSimple (0.00s) 132 | 133 | FAIL 134 | ``` 135 | 136 | By adding `mockopts.WithoutStackTrace()` to the `SetUp` function, we can disable stack trace printing: 137 | ```go 138 | func TestSimple(t *testing.T) { 139 | ctrl := NewMockController(t, mockopts.WithoutStackTrace()) 140 | greeter := Mock[Greeter](ctrl) 141 | When(greeter.Greet("Jane")).ThenReturn("hello world") 142 | greeter.Greet("John") 143 | VerifyNoMoreInteractions(greeter) 144 | } 145 | ``` 146 | 147 | Now the error will look like this: 148 | ``` 149 | === RUN TestSimple 150 | reporter.go:75: At: 151 | go/pkg/mod/github.com/ovechkin-dm/mockio@v0.7.2/registry/registry.go:130 +0x45 152 | Cause: 153 | No more interactions expected, but unverified interactions found: 154 | Greeter.Greet(John) at demo/hello_test.go:17 +0x10b 155 | --- FAIL: TestSimple (0.00s) 156 | 157 | FAIL 158 | ``` -------------------------------------------------------------------------------- /docs/features/error-reporting.md: -------------------------------------------------------------------------------- 1 | # Error reporting 2 | 3 | `Mockio` library supports providing custom error reporting in `NewMockController` function. 4 | This can be helpful if you want to introduce custom error reporting or logging. 5 | Reporter should implement `ErrorReporter` interface. 6 | ```go 7 | type ErrorReporter interface { 8 | Fatalf(format string, args ...any) 9 | Errorf(format string, args ...any) 10 | Cleanup(func()) 11 | } 12 | 13 | ``` 14 | 15 | * `Fatalf` - should be used to report fatal errors. It should stop the test execution. 16 | * `Errorf` - should be used to report non-fatal errors. It should continue the test execution. 17 | * `Cleanup` - should be used to register a cleanup function. It should be called after the test execution. 18 | 19 | ## Error output 20 | 21 | ### Incorrect `When` usage 22 | 23 | Example: 24 | 25 | ```go 26 | When(1) 27 | ``` 28 | 29 | Output: 30 | ``` 31 | At: 32 | /demo/error_reporting_test.go:22 +0xad 33 | Cause: 34 | When() requires an argument which has to be 'a method call on a mock'. 35 | For example: When(mock.GetArticles()).ThenReturn(articles) 36 | ``` 37 | 38 | ### Non-mock verification 39 | 40 | Example: 41 | 42 | ```go 43 | Verify(100, Once()) 44 | ``` 45 | 46 | Output: 47 | ``` 48 | At: 49 | /demo/error_reporting_test.go:46 +0x105 50 | Cause: 51 | Argument passed to Verify() is 100 and is not a mock, or a mock created in a different goroutine. 52 | Make sure you place the parenthesis correctly. 53 | Example of correct verification: 54 | Verify(mock, Times(10)).SomeMethod() 55 | ``` 56 | 57 | ### Invalid use of matchers 58 | 59 | Example: 60 | 61 | ```go 62 | When(mock.Baz(AnyInt(), AnyInt(), 10)).ThenReturn(10) 63 | ``` 64 | 65 | Output: 66 | ``` 67 | At: 68 | /demo/error_reporting_test.go:55 +0x110 69 | Cause: 70 | Invalid use of matchers 71 | 3 expected, 2 recorded: 72 | /demo/error_reporting_test.go:55 +0xab 73 | /demo/error_reporting_test.go:55 +0xbc 74 | method: 75 | Foo.Baz(int, int, int) int 76 | expected: 77 | (int,int,int) 78 | got: 79 | (Any[int],Any[int]) 80 | This can happen for 2 reasons: 81 | 1. Declaration of matcher outside When() call 82 | 2. Mixing matchers and exact values in When() call. Is this case, consider using "Exact" matcher. 83 | ``` 84 | 85 | ### Expected method call 86 | 87 | Example: 88 | 89 | ```go 90 | When(mock.Baz(AnyInt(), AnyInt(), AnyInt())).ThenReturn(10) 91 | _ = mock.Baz(10, 10, 11) 92 | Verify(mock, Once()).Baz(AnyInt(), AnyInt(), Exact(10)) 93 | ``` 94 | 95 | Output: 96 | ``` 97 | At: 98 | /demo/error_reporting_test.go:88 +0x262 99 | Cause: 100 | expected num method calls: 1, got : 0 101 | Foo.Baz(Any[int], Any[int], Exact(10)) 102 | However, there were other interactions with this method: 103 | Foo.Baz(10, 10, 11) at /demo/error_reporting_test.go:87 +0x193 104 | ``` 105 | 106 | ### Number of method calls 107 | 108 | Example: 109 | 110 | ```go 111 | When(mock.Baz(AnyInt(), AnyInt(), AnyInt())).ThenReturn(10) 112 | _ = mock.Baz(10, 10, 10) 113 | Verify(mock, Times(20)).Baz(AnyInt(), AnyInt(), AnyInt()) 114 | ``` 115 | 116 | Output: 117 | ``` 118 | At: 119 | /demo/error_reporting_test.go:121 +0x25a 120 | Cause: 121 | expected num method calls: 20, got : 1 122 | Foo.Baz(Any[int], Any[int], Any[int]) 123 | Invocations: 124 | /demo/error_reporting_test.go:120 +0x191 125 | ``` 126 | 127 | ### Empty captor 128 | 129 | Example: 130 | 131 | ```go 132 | c := Captor[int]() 133 | _ = c.Last() 134 | ``` 135 | 136 | Output: 137 | ``` 138 | At: 139 | /demo/error_reporting_test.go:130 +0x92 140 | Cause: 141 | no values were captured for captor 142 | ``` 143 | 144 | ### Invalid return values 145 | 146 | Example: 147 | 148 | ```go 149 | When(mock.Baz(AnyInt(), AnyInt(), AnyInt())).ThenReturn(10, 20) 150 | ``` 151 | 152 | Output: 153 | ``` 154 | At: 155 | /demo/error_reporting_test.go:140 +0x1a7 156 | Cause: 157 | invalid return values 158 | expected: 159 | Foo.Baz(int, int, int) int 160 | got: 161 | Foo.Baz(int, int, int) (string, int) 162 | ``` 163 | 164 | ### No more interactions 165 | 166 | Example: 167 | 168 | ```go 169 | When(mock.Baz(AnyInt(), AnyInt(), AnyInt())).ThenReturn("test", 10) 170 | _ = mock.Baz(10, 10, 10) 171 | _ = mock.Baz(10, 20, 10) 172 | VerifyNoMoreInteractions(mock) 173 | ``` 174 | 175 | Output: 176 | ``` 177 | At: 178 | /demo/mockio/registry/registry.go:130 +0x45 179 | Cause: 180 | No more interactions expected, but unverified interactions found: 181 | Foo.Baz(10, 10, 10) at /demo/error_reporting_test.go:150 +0x1a8 182 | Foo.Baz(10, 20, 10) at /demo/error_reporting_test.go:151 +0x1c6 183 | ``` 184 | 185 | ### Unexpected matcher declaration 186 | 187 | Example: 188 | 189 | ```go 190 | When(mock.Baz(AnyInt(), AnyInt(), AnyInt())).ThenReturn(10) 191 | mock.Baz(AnyInt(), AnyInt(), AnyInt()) 192 | Verify(mock, Once()).Baz(10, 10, 10) 193 | ``` 194 | 195 | ```go 196 | At: 197 | /demo/error_reporting_test.go:175 +0x23f 198 | Cause: 199 | Unexpected matchers declaration. 200 | at /demo/error_reporting_test.go:174 +0x185 201 | at /demo/error_reporting_test.go:174 +0x196 202 | at /demo/error_reporting_test.go:174 +0x1a7 203 | Matchers can only be used inside When() method call. 204 | ``` -------------------------------------------------------------------------------- /docs/features/matchers.md: -------------------------------------------------------------------------------- 1 | # Matchers 2 | MockIO library provides a lot of ways to match arguments of the method calls. 3 | Matchers are used to define the expected arguments of the method calls. 4 | 5 | We will use the following interface for the examples: 6 | ```go 7 | package main 8 | 9 | import ( 10 | . "github.com/ovechkin-dm/mockio/v2/mock" 11 | "testing" 12 | ) 13 | 14 | type Greeter interface { 15 | Greet(name any) string 16 | } 17 | 18 | func TestSimple(t *testing.T) { 19 | ctrl := NewMockController(t) 20 | greeter := Mock[Greeter](ctrl) 21 | When(greeter.Greet("Jane")).ThenReturn("hello world") 22 | greeter.Greet("John") 23 | } 24 | ``` 25 | 26 | ## Any 27 | The `Any[T]()` matcher matches any value of the type `T`. 28 | 29 | This test will succeed: 30 | ```go 31 | func TestSimple(t *testing.T) { 32 | ctrl := NewMockController(t) 33 | greeter := Mock[Greeter](ctrl) 34 | When(greeter.Greet(Any[string]())).ThenReturn("hello world") 35 | if greeter.Greet("John") != "hello world" { 36 | t.Error("Expected 'hello world'") 37 | } 38 | } 39 | ``` 40 | 41 | ## AnyInt 42 | The `AnyInt()` matcher matches any integer value. 43 | 44 | This test will succeed: 45 | ```go 46 | func TestSimple(t *testing.T) { 47 | ctrl := NewMockController(t) 48 | greeter := Mock[Greeter](ctrl) 49 | When(greeter.Greet(Any[int]())).ThenReturn("hello world") 50 | if greeter.Greet(10) != "hello world" { 51 | t.Error("Expected 'hello world'") 52 | } 53 | } 54 | ``` 55 | This test will fail, because the argument is not an integer: 56 | ```go 57 | func TestSimple(t *testing.T) { 58 | ctrl := NewMockController(t) 59 | greeter := Mock[Greeter](ctrl) 60 | When(greeter.Greet(Any[int]())).ThenReturn("hello world") 61 | if greeter.Greet("John") != "hello world" { 62 | t.Error("Expected 'hello world'") 63 | } 64 | } 65 | ``` 66 | 67 | ## AnyString 68 | The `AnyString()` matcher matches any string value. 69 | 70 | This test will succeed: 71 | ```go 72 | func TestSimple(t *testing.T) { 73 | ctrl := NewMockController(t) 74 | greeter := Mock[Greeter](ctrl) 75 | When(greeter.Greet(Any[string]())).ThenReturn("hello world") 76 | if greeter.Greet("John") != "hello world" { 77 | t.Error("Expected 'hello world'") 78 | } 79 | } 80 | ``` 81 | This test will fail, because the argument is not a string: 82 | ```go 83 | func TestSimple(t *testing.T) { 84 | ctrl := NewMockController(t) 85 | greeter := Mock[Greeter](ctrl) 86 | When(greeter.Greet(Any[int]())).ThenReturn("hello world") 87 | if greeter.Greet(10) != "hello world" { 88 | t.Error("Expected 'hello world'") 89 | } 90 | } 91 | ``` 92 | 93 | ## AnyInterface 94 | The `AnyInterface()` matcher matches any value of any type. 95 | 96 | This test will succeed: 97 | ```go 98 | func TestSimple(t *testing.T) { 99 | ctrl := NewMockController(t) 100 | greeter := Mock[Greeter](ctrl) 101 | When(greeter.Greet(AnyInterface())).ThenReturn("hello world") 102 | if greeter.Greet("John") != "hello world" { 103 | t.Error("Expected 'hello world'") 104 | } 105 | } 106 | ``` 107 | 108 | This test will also succeed: 109 | ```go 110 | func TestSimple(t *testing.T) { 111 | ctrl := NewMockController(t) 112 | greeter := Mock[Greeter](ctrl) 113 | When(greeter.Greet(AnyInterface())).ThenReturn("hello world") 114 | if greeter.Greet(10) != "hello world" { 115 | t.Error("Expected 'hello world'") 116 | } 117 | } 118 | ``` 119 | 120 | ## AnyContext 121 | The `AnyContext()` matcher matches any context.Context value. 122 | 123 | This test will succeed: 124 | ```go 125 | func TestSimple(t *testing.T) { 126 | ctrl := NewMockController(t) 127 | greeter := Mock[Greeter](ctrl) 128 | When(greeter.Greet(AnyContext())).ThenReturn("hello world") 129 | if greeter.Greet(context.Background()) != "hello world" { 130 | t.Error("Expected 'hello world'") 131 | } 132 | } 133 | ``` 134 | 135 | ## AnyOfType 136 | The `AnyOfType[T](t T)` matcher matches any value of the type `T` or its subtype. It is useful for type inference. 137 | 138 | This test will succeed: 139 | ```go 140 | func TestSimple(t *testing.T) { 141 | ctrl := NewMockController(t) 142 | greeter := Mock[Greeter](ctrl) 143 | When(greeter.Greet(AnyOfType(10))).ThenReturn("hello world") 144 | if greeter.Greet(10) != "hello world" { 145 | t.Error("Expected 'hello world'") 146 | } 147 | } 148 | ``` 149 | Note that when we are using AnyOfType, we don't need to specify the type explicitly. 150 | 151 | ## Nil 152 | The `Nil[T]()` matcher matches any nil value of the type `T`. 153 | 154 | This test will succeed: 155 | ```go 156 | func TestSimple(t *testing.T) { 157 | ctrl := NewMockController(t) 158 | greeter := Mock[Greeter](ctrl) 159 | When(greeter.Greet(Nil[any]())).ThenReturn("hello world") 160 | if greeter.Greet(nil) != "hello world" { 161 | t.Error("Expected 'hello world'") 162 | } 163 | } 164 | ``` 165 | 166 | ## NotNil 167 | The `NotNil[T]()` matcher matches any non-nil value of the type `T`. 168 | 169 | This test will succeed: 170 | ```go 171 | func TestSimple(t *testing.T) { 172 | ctrl := NewMockController(t) 173 | greeter := Mock[Greeter](ctrl) 174 | When(greeter.Greet(NotNil[any]())).ThenReturn("hello world") 175 | if greeter.Greet("John") != "hello world" { 176 | t.Error("Expected 'hello world'") 177 | } 178 | } 179 | ``` 180 | 181 | This test will fail: 182 | ```go 183 | func TestSimple(t *testing.T) { 184 | ctrl := NewMockController(t) 185 | greeter := Mock[Greeter](ctrl) 186 | When(greeter.Greet(NotNil[any]())).ThenReturn("hello world") 187 | if greeter.Greet(nil) != "hello world" { 188 | t.Error("Expected 'hello world'") 189 | } 190 | } 191 | ``` 192 | 193 | ## Regex 194 | The `Regex(pattern string)` matcher matches any string that matches the regular expression `pattern`. 195 | 196 | This test will succeed: 197 | ```go 198 | func TestSimple(t *testing.T) { 199 | ctrl := NewMockController(t) 200 | greeter := Mock[Greeter](ctrl) 201 | When(greeter.Greet(Regex("J.*"))).ThenReturn("hello world") 202 | if greeter.Greet("John") != "hello world" { 203 | t.Error("Expected 'hello world'") 204 | } 205 | } 206 | ``` 207 | 208 | ## Substring 209 | The `Substring(sub string)` matcher matches any string that contains the substring `sub`. 210 | 211 | This test will succeed: 212 | ```go 213 | func TestSimple(t *testing.T) { 214 | ctrl := NewMockController(t) 215 | greeter := Mock[Greeter](ctrl) 216 | When(greeter.Greet(Substring("oh"))).ThenReturn("hello world") 217 | if greeter.Greet("John") != "hello world" { 218 | t.Error("Expected 'hello world'") 219 | } 220 | } 221 | ``` 222 | 223 | ## SliceLen 224 | The `SliceLen(length int)` matcher matches any slice with the length `length`. 225 | 226 | This test will succeed: 227 | ```go 228 | func TestSimple(t *testing.T) { 229 | ctrl := NewMockController(t) 230 | greeter := Mock[Greeter](ctrl) 231 | When(greeter.Greet(SliceLen[int](2))).ThenReturn("hello world") 232 | if greeter.Greet([]int{1, 2}) != "hello world" { 233 | t.Error("Expected 'hello world'") 234 | } 235 | } 236 | ``` 237 | 238 | This test will fail: 239 | ```go 240 | func TestSimple(t *testing.T) { 241 | ctrl := NewMockController(t) 242 | greeter := Mock[Greeter](ctrl) 243 | When(greeter.Greet(SliceLen[int](2))).ThenReturn("hello world") 244 | if greeter.Greet([]int{1, 2, 3}) != "hello world" { 245 | t.Error("Expected 'hello world'") 246 | } 247 | } 248 | ``` 249 | 250 | ## MapLen 251 | The `MapLen(length int)` matcher matches any map with the length `length`. 252 | 253 | This test will succeed: 254 | ```go 255 | func TestSimple(t *testing.T) { 256 | ctrl := NewMockController(t) 257 | greeter := Mock[Greeter](ctrl) 258 | When(greeter.Greet(MapLen[int, string](2))).ThenReturn("hello world") 259 | if greeter.Greet(map[int]string{1: "one", 2: "two"}) != "hello world" { 260 | t.Error("Expected 'hello world'") 261 | } 262 | } 263 | ``` 264 | 265 | This test will fail: 266 | ```go 267 | func TestSimple(t *testing.T) { 268 | ctrl := NewMockController(t) 269 | greeter := Mock[Greeter](ctrl) 270 | When(greeter.Greet(MapLen[int, string](2))).ThenReturn("hello world") 271 | if greeter.Greet(map[int]string{1: "one", 2: "two", 3: "three"}) != "hello world" { 272 | t.Error("Expected 'hello world'") 273 | } 274 | } 275 | ``` 276 | 277 | ## SliceContains 278 | The `SliceContains[T any](values ...T)` matcher matches any slice that contains all the values `values`. 279 | 280 | This test will succeed: 281 | ```go 282 | func TestSimple(t *testing.T) { 283 | ctrl := NewMockController(t) 284 | greeter := Mock[Greeter](ctrl) 285 | When(greeter.Greet(SliceContains[int](1, 2))).ThenReturn("hello world") 286 | if greeter.Greet([]int{1, 2, 3}) != "hello world" { 287 | t.Error("Expected 'hello world'") 288 | } 289 | } 290 | ``` 291 | 292 | This test will fail: 293 | ```go 294 | func TestSimple(t *testing.T) { 295 | ctrl := NewMockController(t) 296 | greeter := Mock[Greeter](ctrl) 297 | When(greeter.Greet(SliceContains[int](1, 2))).ThenReturn("hello world") 298 | if greeter.Greet([]int{1, 3}) != "hello world" { 299 | t.Error("Expected 'hello world'") 300 | } 301 | } 302 | ``` 303 | 304 | ## MapContains 305 | The `MapContains[K any, V any](keys ...K)` matcher matches any map that contains all the keys `keys`. 306 | 307 | This test will succeed: 308 | ```go 309 | func TestSimple(t *testing.T) { 310 | ctrl := NewMockController(t) 311 | greeter := Mock[Greeter](ctrl) 312 | When(greeter.Greet(MapContains[int, string](1, 2))).ThenReturn("hello world") 313 | if greeter.Greet(map[int]string{1: "one", 2: "two", 3: "three"}) != "hello world" { 314 | t.Error("Expected 'hello world'") 315 | } 316 | } 317 | ``` 318 | 319 | This test will fail: 320 | ```go 321 | func TestSimple(t *testing.T) { 322 | ctrl := NewMockController(t) 323 | greeter := Mock[Greeter](ctrl) 324 | When(greeter.Greet(MapContains[int, string](1, 2))).ThenReturn("hello world") 325 | if greeter.Greet(map[int]string{1: "one", 3: "three"}) != "hello world" { 326 | t.Error("Expected 'hello world'") 327 | } 328 | } 329 | ``` 330 | 331 | ## SliceEqualUnordered 332 | 333 | The `SliceEqualUnordered[T any](values []T)` matcher matches any slice that contains the same elements as `values`, but in any order. 334 | 335 | This test will succeed: 336 | ```go 337 | func TestSimple(t *testing.T) { 338 | ctrl := NewMockController(t) 339 | greeter := Mock[Greeter](ctrl) 340 | When(greeter.Greet(SliceEqualUnordered[int](1, 2))).ThenReturn("hello world") 341 | if greeter.Greet([]int{2, 1}) != "hello world" { 342 | t.Error("Expected 'hello world'") 343 | } 344 | } 345 | ``` 346 | 347 | This test will fail: 348 | ```go 349 | func TestSimple(t *testing.T) { 350 | ctrl := NewMockController(t) 351 | greeter := Mock[Greeter](ctrl) 352 | When(greeter.Greet(SliceEqualUnordered[int](1, 2))).ThenReturn("hello world") 353 | if greeter.Greet([]int{1, 3}) != "hello world" { 354 | t.Error("Expected 'hello world'") 355 | } 356 | } 357 | ``` 358 | 359 | ## Exact 360 | 361 | The `Exact` matcher matches any value that is equal to the expected value. 362 | `Exact` uses `==` operator to compare values. 363 | 364 | This test will succeed: 365 | ```go 366 | func TestSimple(t *testing.T) { 367 | ctrl := NewMockController(t) 368 | greeter := Mock[Greeter](ctrl) 369 | world1 := "world" 370 | When(greeter.Greet(Exact(&world1))).ThenReturn("hello world") 371 | if greeter.Greet(&world1) != "hello world" { 372 | t.Error("Expected 'hello world'") 373 | } 374 | } 375 | ``` 376 | 377 | However, this test will fail, because although the values are equal, they are different pointers: 378 | ```go 379 | func TestSimple(t *testing.T) { 380 | ctrl := NewMockController(t) 381 | greeter := Mock[Greeter](ctrl) 382 | world1 := "world" 383 | world2 := "world" 384 | When(greeter.Greet(Exact(&world1))).ThenReturn("hello world") 385 | if greeter.Greet(&world2) != "hello world" { 386 | t.Error("Expected 'hello world'") 387 | } 388 | } 389 | ``` 390 | 391 | ## Equal 392 | 393 | The `Equal` matcher matches any value that is equal to the expected value. `Equal` uses `reflect.DeepEqual` to compare values. 394 | 395 | This test will succeed, because `reflect.DeepEqual` compares values by their content: 396 | ```go 397 | func TestSimple(t *testing.T) { 398 | ctrl := NewMockController(t) 399 | greeter := Mock[Greeter](ctrl) 400 | world1 := "world" 401 | world2 := "world" 402 | When(greeter.Greet(Equal(&world1))).ThenReturn("hello world") 403 | if greeter.Greet(&world2) != "hello world" { 404 | t.Error("Expected 'hello world'") 405 | } 406 | } 407 | ``` 408 | 409 | ## NotEqual 410 | 411 | The `NotEqual` matcher matches any value that is not equal to the expected value. `NotEqual` uses `reflect.DeepEqual` to compare values. 412 | 413 | This test will succeed: 414 | ```go 415 | func TestSimple(t *testing.T) { 416 | ctrl := NewMockController(t) 417 | greeter := Mock[Greeter](ctrl) 418 | When(greeter.Greet(NotEqual("John"))).ThenReturn("hello world") 419 | if greeter.Greet("world") != "hello world" { 420 | t.Error("Expected 'hello John'") 421 | } 422 | } 423 | 424 | ``` 425 | 426 | ## OneOf 427 | 428 | The `OneOf` matcher matches any value that is equal to one of the expected values. `OneOf` uses `reflect.DeepEqual` to compare values. 429 | 430 | This test will succeed: 431 | ```go 432 | func TestSimple(t *testing.T) { 433 | ctrl := NewMockController(t) 434 | greeter := Mock[Greeter](ctrl) 435 | When(greeter.Greet(OneOf("John", "Jane"))).ThenReturn("hello John or Jane") 436 | if greeter.Greet("Jane") != "hello John or Jane" { 437 | t.Error("expected 'hello John or Jane'") 438 | } 439 | } 440 | ``` 441 | ## Custom matcher 442 | 443 | Here is an example of a custom matcher that matches odd numbers only: 444 | 445 | ```go 446 | func TestSimple(t *testing.T) { 447 | ctrl := NewMockController(t) 448 | greeter := Mock[Greeter](ctrl) 449 | odd := CreateMatcher[int]("odd", func(args []any, v int) bool { 450 | return v%2 == 1 451 | }) 452 | When(greeter.Greet(odd())).ThenReturn("hello odd number") 453 | if greeter.Greet(1) != "hello odd number" { 454 | t.Error("expected ''hello odd number''") 455 | } 456 | } 457 | ``` 458 | 459 | -------------------------------------------------------------------------------- /docs/features/method-stubbing.md: -------------------------------------------------------------------------------- 1 | # Method stubbing 2 | 3 | Method stubbing is a technique used in unit testing to replace a method with a stub. A stub is a small piece of code 4 | that simulates the behavior of the method it replaces. This allows you to test the behavior of the code that calls the 5 | method without actually executing the method itself. 6 | 7 | Basic usage of method stubbing in Mockio looks like this: 8 | 9 | ```go 10 | When(mock.SomeMethod(AnyInt())).ThenReturn("some value") 11 | ``` 12 | 13 | * `When` is a function that takes a method call as an argument and returns a `Returner` object. 14 | * Inside the method call argument you can use any matcher from the library's API. In this example we used `AnyInt()` matcher. 15 | * `ThenReturn` is a method of the `Returner` 16 | 17 | This is basic usage of method stubbing. But there are also some useful extensions to this API. 18 | 19 | ## When 20 | 21 | `When` is a function that allows you to stub a method. 22 | Keep in mind, that `When` is a generic function, so it does not provide any type check on return value. 23 | 24 | 25 | ## WhenSingle 26 | 27 | `WhenSingle` is a function that allows you to stub a method to return a single value. 28 | It is almost the same as `When`, but it provides additional type check on return value. 29 | 30 | Consider Following interface: 31 | ```go 32 | type Foo interface { 33 | Bar(int) string 34 | } 35 | ``` 36 | 37 | You can stub `Bar` method like this: 38 | ```go 39 | WhenSingle(mock.Bar(AnyInt())).ThenReturn("some value") 40 | ``` 41 | 42 | However, this will not compile: 43 | ```go 44 | WhenSingle(mock.Bar(AnyInt())).ThenReturn(42) 45 | ``` 46 | 47 | But this will: 48 | ```go 49 | When(mock.Bar(AnyInt())).ThenReturn(42) 50 | ``` 51 | 52 | ## WhenDouble 53 | 54 | `WhenDouble` is a function that allows you to stub a method to return two values. 55 | It is almost the same as `When`, but it provides additional type check on return values. 56 | 57 | Consider Following interface: 58 | ```go 59 | type Foo interface { 60 | Bar(int) (string, error) 61 | } 62 | ``` 63 | 64 | You can stub `Bar` method like this: 65 | ```go 66 | WhenDouble(mock.Bar(AnyInt())).ThenReturn("some value", nil) 67 | ``` 68 | 69 | However, this will not compile: 70 | ```go 71 | WhenDouble(mock.Bar(AnyInt())).ThenReturn("some value", 42) 72 | ``` 73 | 74 | But this will: 75 | ```go 76 | When(mock.Bar(AnyInt())).ThenReturn("some value", 42) 77 | ``` 78 | 79 | ## ThenAnswer 80 | 81 | `Answer` is a function that allows you to stub a method to return a value based on the arguments passed to the method. 82 | 83 | Consider following interface: 84 | ```go 85 | type Foo interface { 86 | Bar(int) string 87 | } 88 | ``` 89 | 90 | You can stub `Bar` method like this: 91 | ```go 92 | ctrl := NewMockController(t) 93 | mock := Mock[Foo](ctrl) 94 | WhenSingle(mock.Bar(AnyInt())).ThenAnswer(func(args []any) string { 95 | return fmt.Sprintf("Hello, %d", args[0].(int)) 96 | }) 97 | ``` 98 | 99 | When `Bar` method is called with argument `42`, it will return `"Hello, 42"`. 100 | 101 | ## ThenReturn 102 | 103 | You can chain multiple `ThenReturn` calls to return different values on subsequent calls: 104 | 105 | ```go 106 | When(mock.SomeMethod(AnyInt())). 107 | ThenReturn("first value"). 108 | ThenReturn("second value") 109 | ``` 110 | 111 | Calling `SomeMethod` first time will return `"first value"`, second time `"second value"`, and so on. 112 | 113 | ## Implicit `Exact` matchers 114 | 115 | Consider following interface: 116 | 117 | ```go 118 | type Foo interface { 119 | Bar(int, int) string 120 | } 121 | 122 | ``` 123 | 124 | To stub `Bar` method, we can use something like this: 125 | ```go 126 | When(mock.Bar(Exact(1), Exact(2))).ThenReturn("some value") 127 | ``` 128 | 129 | However, this can be simplified to: 130 | ```go 131 | When(mock.Bar(1, 2)).ThenReturn("some value") 132 | ``` 133 | 134 | In short, you can omit using matchers when you want to match exact values, but they all should be exact. 135 | For example, this will not work: 136 | ```go 137 | When(mock.Bar(1, Exact(2))).ThenReturn("some value") 138 | ``` 139 | -------------------------------------------------------------------------------- /docs/features/parallel-execution.md: -------------------------------------------------------------------------------- 1 | # Parallel execution 2 | 3 | ## Parallelism 4 | 5 | It is possible to run multiple tests with mockio in parallel using the `--parallel` option. This option is available in the `test` and `run` commands. 6 | 7 | ## Concurrency 8 | 9 | Library supports invoking stubbed methods from different goroutine. 10 | 11 | ```go 12 | func TestParallelSuccess(t *testing.T) { 13 | ctrl := NewMockController(t) 14 | greeter := Mock[Greeter](ctrl) 15 | wg := sync.WaitGroup{} 16 | wg.Add(2) 17 | When(greeter.Greet("John")).ThenReturn("hello world") 18 | go func() { 19 | greeter.Greet("John") 20 | wg.Done() 21 | }() 22 | go func() { 23 | greeter.Greet("John") 24 | wg.Done() 25 | }() 26 | wg.Wait() 27 | Verify(greeter, Times(2)).Greet("John") 28 | } 29 | ``` -------------------------------------------------------------------------------- /docs/features/verification.md: -------------------------------------------------------------------------------- 1 | # Verification 2 | 3 | We will use the following interface for the examples: 4 | ```go 5 | package main 6 | 7 | import ( 8 | . "github.com/ovechkin-dm/mockio/v2/mock" 9 | "testing" 10 | ) 11 | 12 | type Greeter interface { 13 | Greet(name any) string 14 | } 15 | 16 | func TestSimple(t *testing.T) { 17 | ctrl := NewMockController(t) 18 | greeter := Mock[Greeter](ctrl) 19 | When(greeter.Greet("Jane")).ThenReturn("hello world") 20 | greeter.Greet("John") 21 | 22 | } 23 | ``` 24 | 25 | ## Verify 26 | 27 | To verify that a method was called, use the `Verify` function. 28 | If the method was called, the test will pass. If the method was not called, the test will fail. 29 | 30 | This test will succeed: 31 | ```go 32 | func TestSimple(t *testing.T) { 33 | ctrl := NewMockController(t) 34 | greeter := Mock[Greeter](ctrl) 35 | When(greeter.Greet("Jane")).ThenReturn("hello world") 36 | greeter.Greet("John") 37 | Verify(greeter, Once()).Greet("John") 38 | } 39 | ``` 40 | 41 | This test will fail: 42 | ```go 43 | func TestSimple(t *testing.T) { 44 | ctrl := NewMockController(t) 45 | greeter := Mock[Greeter](ctrl) 46 | When(greeter.Greet("Jane")).ThenReturn("hello world") 47 | greeter.Greet("John") 48 | Verify(greeter, Once()).Greet("Jane") 49 | } 50 | ``` 51 | 52 | ### AtLeastOnce 53 | 54 | Verify that a method was called at least once: 55 | ```go 56 | func TestSimple(t *testing.T) { 57 | ctrl := NewMockController(t) 58 | greeter := Mock[Greeter](t) 59 | When(greeter.Greet("Jane")).ThenReturn("hello world") 60 | greeter.Greet("John") 61 | Verify(greeter, AtLeastOnce()).Greet("John") 62 | } 63 | ``` 64 | 65 | ### Once 66 | 67 | Verify that a method was called exactly once: 68 | ```go 69 | func TestSimple(t *testing.T) { 70 | ctrl := NewMockController(t) 71 | greeter := Mock[Greeter](t) 72 | When(greeter.Greet("Jane")).ThenReturn("hello world") 73 | greeter.Greet("John") 74 | Verify(greeter, Once()).Greet("John") 75 | } 76 | ``` 77 | 78 | ### Times 79 | 80 | Verify that a method was called a specific number of times: 81 | ```go 82 | func TestSimple(t *testing.T) { 83 | ctrl := NewMockController(t) 84 | greeter := Mock[Greeter](t) 85 | When(greeter.Greet("Jane")).ThenReturn("hello world") 86 | greeter.Greet("John") 87 | greeter.Greet("John") 88 | Verify(greeter, Times(2)).Greet("John") 89 | } 90 | ``` 91 | 92 | 93 | ## VerifyNoMoreInteractions 94 | 95 | To verify that no other methods were called on the mock object, use the `VerifyNoMoreInteractions` function. 96 | It will fail the test if there are any unverified calls. 97 | 98 | This test will succeed: 99 | ```go 100 | func TestSimple(t *testing.T) { 101 | ctrl := NewMockController(t) 102 | greeter := Mock[Greeter](ctrl) 103 | When(greeter.Greet("Jane")).ThenReturn("hello world") 104 | greeter.Greet("John") 105 | Verify(greeter, Once()).Greet("John") 106 | VerifyNoMoreInteractions(greeter) 107 | } 108 | ``` 109 | 110 | This test will fail: 111 | ```go 112 | func TestSimple(t *testing.T) { 113 | ctrl := NewMockController(t) 114 | greeter := Mock[Greeter](ctrl) 115 | When(greeter.Greet("John")).ThenReturn("hello world") 116 | greeter.Greet("John") 117 | VerifyNoMoreInteractions(greeter) 118 | } 119 | ``` 120 | 121 | ## Verify after `ThenReturn` 122 | 123 | Since it is common to actually verify that a stub was used correctly, you can use the `Verify` function after the `ThenReturn` function: 124 | ```go 125 | func TestSimple(t *testing.T) { 126 | ctrl := NewMockController(t) 127 | greeter := Mock[Greeter](ctrl) 128 | When(greeter.Greet("John")).ThenReturn("hello world").Verify(Once()) 129 | greeter.Greet("John") 130 | VerifyNoMoreInteractions(greeter) 131 | } 132 | ``` 133 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Mockio 2 | 3 | Golang library for mocking without code generation, inspired by Mockito. 4 | 5 | ## Installing library 6 | 7 | Install latest version of the library using `go get` command: 8 | 9 | ```bash 10 | go get -u github.com/ovechkin-dm/mockio/v2 11 | ``` 12 | 13 | ## Creating test 14 | 15 | Let's create an interface that we want to mock: 16 | 17 | ```go 18 | type Greeter interface { 19 | Greet(name string) string 20 | } 21 | ``` 22 | 23 | Now we will use `dot import` to simplify the usage of the library: 24 | 25 | ```go 26 | import ( 27 | ."github.com/ovechkin-dm/mockio/v2/mock" 28 | "testing" 29 | ) 30 | ``` 31 | 32 | Now we can create a mock for the `Greeter` interface, and test it's method `Greet`: 33 | 34 | ```go 35 | func TestGreet(t *testing.T) { 36 | ctrl := NewMockController(t) 37 | m := Mock[Greeter](ctrl) 38 | WhenSingle(m.Greet("John")).ThenReturn("Hello, John!") 39 | if m.Greet("John") != "Hello, John!" { 40 | t.Fail() 41 | } 42 | } 43 | ``` 44 | 45 | ## Full example 46 | Here is the full listing for our simple test: 47 | 48 | ```go 49 | package main 50 | 51 | import ( 52 | . "github.com/ovechkin-dm/mockio/v2/mock" 53 | "testing" 54 | ) 55 | 56 | type Greeter interface { 57 | Greet(name string) string 58 | } 59 | 60 | func TestGreet(t *testing.T) { 61 | ctrl := NewMockController(t) 62 | m := Mock[Greeter](ctrl) 63 | WhenSingle(m.Greet("John")).ThenReturn("Hello, John!") 64 | if m.Greet("John") != "Hello, John!" { 65 | t.Fail() 66 | } 67 | } 68 | 69 | ``` 70 | 71 | That's it! You have created a mock for the `Greeter` interface without any code generation. 72 | As you can see, the library is very simple and easy to use. 73 | And no need to generate mocks for your interfaces. 74 | -------------------------------------------------------------------------------- /docs/limitations.md: -------------------------------------------------------------------------------- 1 | # Limitations 2 | 3 | ## Architecture 4 | 5 | Because library uses assembly code to generate mocks, it is not possible to use it in pure Go code. 6 | This means that you cannot use it in a project that is intended to be cross-compiled to multiple platforms. 7 | 8 | For now supported platforms are: 9 | 10 | - AMD64 11 | - ARM64 12 | 13 | This list may be extended in the future. 14 | 15 | ## Backwards compatibility and new Go versions 16 | 17 | This library is tested for GO 1.18 up to 1.23 18 | 19 | Caution: there is no guarantee that it will work with future versions of Go. 20 | However there is not much that can break the library, so it should be easy to fix it if it stops working. As of latest mockio version, almost all of dependencies on golang internal runtime features were removed. 21 | 22 | Please refer to [go-dyno documentation](https://ovechkin-dm.github.io/go-dyno/latest/) for more information on compatibility. -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | mike @ git+https://github.com/jimporter/mike.git 2 | mkdocs 3 | mkdocs-glightbox 4 | mkdocs-open-in-new-tab 5 | mkdocs-material 6 | cairosvg 7 | pillow -------------------------------------------------------------------------------- /docs/sponsors.md: -------------------------------------------------------------------------------- 1 | # Sponsors list 2 | 3 | Thanks to all the people who supported this project by donating money, 4 | time or resources: 5 | 6 | * [Ovechkin Dmitry](https://github.com/ovechkin-dm) 7 | * [Eray Ates](https://github.com/rytsh) 8 | * [Emilien Puget](https://github.com/emilien-puget) -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ovechkin-dm/mockio/v2 2 | 3 | go 1.21 4 | 5 | require github.com/ovechkin-dm/go-dyno v0.5.2 6 | 7 | require github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a 8 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/ovechkin-dm/go-dyno v0.5.1 h1:hOi36gKwv1GyDHtWD46uq1xL8H7pU/qSgJabmVT0d78= 2 | github.com/ovechkin-dm/go-dyno v0.5.1/go.mod h1:CcJNuo7AbePMoRNpM3i1jC1Rp9kHEMyWozNdWzR+0ys= 3 | github.com/ovechkin-dm/go-dyno v0.5.2 h1:E0YtEtMjW7kE0JgZINQ3Nb2Zr9Iuoi48/c97/A80eqs= 4 | github.com/ovechkin-dm/go-dyno v0.5.2/go.mod h1:CcJNuo7AbePMoRNpM3i1jC1Rp9kHEMyWozNdWzR+0ys= 5 | github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a h1:S+AGcmAESQ0pXCUNnRH7V+bOUIgkSX5qVt2cNKCrm0Q= 6 | github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= 7 | -------------------------------------------------------------------------------- /matchers/capture.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | // ArgumentCaptor is interface that allows capturing arguments 4 | // passed to a mock method call. 5 | // 6 | // The Capture method is used to capture and store a single argument passed to a method call. 7 | // The Last method is used to retrieve the last captured argument. 8 | // The Values method is used to retrieve all captured arguments. 9 | // 10 | // Example usage: 11 | // 12 | // // Create a mock object 13 | // 14 | // m := Mock[Iface]() 15 | // 16 | // // Create captor for int value 17 | // c := Captor[int]() 18 | // 19 | // // Use captor.Capture() inside When expression 20 | // WhenSingle(m.Foo(AnyInt(), c.Capture())).ThenReturn(10) 21 | // 22 | // m.Foo(10, 20) 23 | // capturedValue := c.Last() 24 | // 25 | // fmt.Printf("Captured value: %v\n", capturedValue) 26 | // 27 | // Output: 28 | // 29 | // Captured value: 20 30 | type ArgumentCaptor[T any] interface { 31 | // Capture captures and stores a single argument passed to a method call. 32 | Capture() T 33 | // Last retrieves the last captured argument. 34 | Last() T 35 | // Values retrieves all captured arguments. 36 | Values() []T 37 | } 38 | -------------------------------------------------------------------------------- /matchers/controller.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/ovechkin-dm/mockio/v2/config" 7 | ) 8 | 9 | type MockEnv struct { 10 | Reporter ErrorReporter 11 | Config *config.MockConfig 12 | } 13 | 14 | type MockController struct { 15 | Env *MockEnv 16 | MockFactory MockFactory 17 | } 18 | 19 | type MockFactory interface { 20 | BuildHandler(env *MockEnv, ifaceType reflect.Type) Handler 21 | } 22 | 23 | type Handler interface { 24 | Handle(method reflect.Method, values []reflect.Value) []reflect.Value 25 | } 26 | -------------------------------------------------------------------------------- /matchers/matchers.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | // Answer is a type alias for a function that can be used as a return value for mock function calls. 4 | // This function takes a variable number of interface{} arguments and returns a slice of interface{} values. 5 | // Each value in the returned slice corresponds to a return value for the mock function call. 6 | // This type can be used to provide dynamic return values based on the input arguments passed to the mock function call. 7 | type Answer = func(args []any) []any 8 | 9 | // Matcher interface represents an object capable of matching method calls to specific criteria. 10 | // 11 | // A Matcher should implement the Match method, which takes a MethodCall and an actual parameter, and returns true 12 | // if the parameter satisfies the criteria defined by the Matcher. 13 | // 14 | // A Matcher should also implement the Description method, which returns a string describing the criteria defined by 15 | // the Matcher. 16 | // 17 | // Matchers can be used in conjunction with the Match function to create flexible and powerful method call matching 18 | // capabilities. 19 | type Matcher[T any] interface { 20 | // Description returns a string describing the criteria defined by the Matcher. 21 | Description() string 22 | 23 | // Match returns true if the given method call satisfies the criteria defined by the Matcher. 24 | // The actual parameter represents the actual value passed to method. 25 | // The allArgs parameter represents all the arguments that were passed to a method. 26 | Match(allArgs []any, actual T) bool 27 | } 28 | -------------------------------------------------------------------------------- /matchers/reporter.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | // ErrorReporter is an interface for reporting errors during test execution. 4 | // Implementations of this interface should provide a way to fail the test with a message. 5 | type ErrorReporter interface { 6 | // Fatalf reports an error and fails the test execution. 7 | // It formats the message according to a format specifier and arguments 8 | // It can be used to report an error and provide additional context about the error. 9 | Fatalf(format string, args ...any) 10 | // Errorf reports an error and continues the test execution. 11 | // It formats the message according to a format specifier and arguments 12 | // It can be used to report an error and provide additional context about the error. 13 | Errorf(format string, args ...any) 14 | // Cleanup adds hooks that are used to clean up data after test was executed. 15 | Cleanup(func()) 16 | } 17 | -------------------------------------------------------------------------------- /matchers/returners.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | type Returner interface { 4 | Verify(verifier MethodVerifier) 5 | } 6 | 7 | // ReturnerSingle is interface that defines methods for returning a value or an answer 8 | // for a mock function with one argument. 9 | type ReturnerSingle[T any] interface { 10 | Returner 11 | // ThenReturn sets the return value for the mock function with one argument. 12 | // The return value must be of type T. 13 | ThenReturn(value T) ReturnerSingle[T] 14 | // ThenAnswer sets a function that will be called when the mock function is 15 | // called with one argument. The function must take a variable number of 16 | // arguments of type interface{} and return a value of type T. 17 | ThenAnswer(func(args []any) T) ReturnerSingle[T] 18 | } 19 | 20 | // ReturnerDouble is an interface that provides methods to define the returned value and error of a mock function with a single argument. 21 | // ThenReturn method sets the return value and error of the mocked function to the provided value and error respectively. 22 | // ThenAnswer method sets the return value and error of the mocked function to the value and error returned by the provided function respectively. 23 | type ReturnerDouble[A any, B any] interface { 24 | Returner 25 | // ThenReturn sets the return value and error of the mocked function to the provided value and error respectively. 26 | ThenReturn(a A, b B) ReturnerDouble[A, B] 27 | // ThenAnswer sets the return value and error of the mocked function to the value and error returned by the provided function respectively. 28 | ThenAnswer(func(args []any) (A, B)) ReturnerDouble[A, B] 29 | } 30 | 31 | // ReturnerAll is a type that defines the methods for returning and answering values for 32 | // a method call with multiple return values. It is returned by the When method. 33 | type ReturnerAll interface { 34 | Returner 35 | // ThenReturn sets the return values for the method call. 36 | // The number and types of the values should match the signature of the method being mocked. 37 | // This method can be called multiple times to set up different return values 38 | // for different calls to the same method with the same arguments. 39 | ThenReturn(values ...any) ReturnerAll 40 | 41 | // ThenAnswer sets a function that will be called to calculate the return values for the method call. 42 | // The function should have the same signature as the method being mocked. 43 | // This method can be called multiple times to set up different answer functions 44 | // for different calls to the same method with the same arguments. 45 | ThenAnswer(answer Answer) ReturnerAll 46 | } 47 | -------------------------------------------------------------------------------- /matchers/verification.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | type MethodVerificationData struct { 9 | NumMethodCalls int 10 | } 11 | 12 | type InvocationData struct { 13 | MethodType reflect.Method 14 | MethodName string 15 | Args []reflect.Value 16 | } 17 | 18 | type MethodVerifier interface { 19 | Verify(data *MethodVerificationData) error 20 | } 21 | 22 | func AtLeastOnce() MethodVerifier { 23 | return MethodVerifierFromFunc(func(data *MethodVerificationData) error { 24 | if data.NumMethodCalls <= 0 { 25 | return fmt.Errorf("expected num method calls: atLeastOnce, got: %d", data.NumMethodCalls) 26 | } 27 | return nil 28 | }) 29 | } 30 | 31 | func Times(n int) MethodVerifier { 32 | return MethodVerifierFromFunc(func(data *MethodVerificationData) error { 33 | if data.NumMethodCalls != n { 34 | return fmt.Errorf("expected num method calls: %d, got : %d", n, data.NumMethodCalls) 35 | } 36 | return nil 37 | }) 38 | } 39 | 40 | func MethodVerifierFromFunc(f func(data *MethodVerificationData) error) MethodVerifier { 41 | return &methodVerifierImpl{ 42 | f: f, 43 | } 44 | } 45 | 46 | type methodVerifierImpl struct { 47 | f func(data *MethodVerificationData) error 48 | } 49 | 50 | func (m *methodVerifierImpl) Verify(data *MethodVerificationData) error { 51 | return m.f(data) 52 | } 53 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: mockio 2 | site_url: https://ovechkin-dm.github.io/mockio/ 3 | site_description: >- 4 | Mock library for Go 5 | 6 | repo_name: ovechkin-dm/mockio 7 | repo_url: https://github.com/ovechkin-dm/mockio 8 | 9 | theme: 10 | name: material 11 | icon: 12 | logo: fontawesome/brands/golang 13 | palette: 14 | - media: "(prefers-color-scheme: dark)" 15 | scheme: slate 16 | primary: purple 17 | toggle: 18 | icon: material/brightness-4 19 | name: Switch to light mode 20 | - media: "(prefers-color-scheme: light)" 21 | scheme: default 22 | primary: purple 23 | toggle: 24 | icon: material/brightness-7 25 | name: Switch to dark mode 26 | features: 27 | - content.code.annotate 28 | - content.code.copy 29 | - navigation.indexes 30 | - navigation.sections 31 | - navigation.tracking 32 | - toc.follow 33 | markdown_extensions: 34 | - admonition 35 | - attr_list 36 | - md_in_html 37 | - pymdownx.emoji: 38 | emoji_index: !!python/name:material.extensions.emoji.twemoji 39 | emoji_generator: !!python/name:material.extensions.emoji.to_svg 40 | - pymdownx.details 41 | - pymdownx.highlight: 42 | anchor_linenums: true 43 | auto_title: true 44 | - pymdownx.inlinehilite 45 | - pymdownx.magiclink 46 | - pymdownx.superfences 47 | - pymdownx.tabbed: 48 | alternate_style: true 49 | - toc: 50 | permalink: true 51 | 52 | 53 | nav: 54 | - Home: index.md 55 | - Features: 56 | - Method stubbing: features/method-stubbing.md 57 | - Matchers: features/matchers.md 58 | - Verification: features/verification.md 59 | - Configuration: features/configuration.md 60 | - Argument captors: features/captors.md 61 | - Parallel execution: features/parallel-execution.md 62 | - Error reporting: features/error-reporting.md 63 | - Code generation: features/code-generation.md 64 | - Limitations: limitations.md 65 | - Sponsors: sponsors.md 66 | 67 | extra_css: 68 | - stylesheets/extra.css 69 | 70 | extra_javascript: 71 | - https://unpkg.com/tablesort@5.3.0/dist/tablesort.min.js 72 | - javascripts/tablesort.js 73 | 74 | extra: 75 | version: 76 | provider: mike 77 | 78 | plugins: 79 | - glightbox 80 | - mike: 81 | alias_type: copy 82 | canonical_version: latest 83 | - open-in-new-tab 84 | - search 85 | - social -------------------------------------------------------------------------------- /mock/api.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "reflect" 7 | "regexp" 8 | "strings" 9 | 10 | "github.com/ovechkin-dm/mockio/v2/config" 11 | "github.com/ovechkin-dm/mockio/v2/matchers" 12 | "github.com/ovechkin-dm/mockio/v2/registry" 13 | ) 14 | 15 | // Mock returns a mock object that implements the specified interface or type. 16 | // The returned object can be used to set up mock behaviors for its methods. 17 | // 18 | // Example usage: 19 | // 20 | // type MyInterface interface { 21 | // MyMethod(arg1 string, arg2 int) (string, error) 22 | // } 23 | // 24 | // func TestMyFunction(t *testing.T) { 25 | // // Create controller 26 | // ctrl := NewMockController(t) 27 | // 28 | // // Create a mock object that implements MyInterface 29 | // myMock := Mock[MyInterface](ctrl) 30 | // 31 | // // Set up a mock behavior for the MyMethod method 32 | // WhenSingle(myMock.MyMethod("foo", 42)).ThenReturn("bar") 33 | // 34 | // // Call the method on the mock object 35 | // result, err := myMock.MyMethod("foo", 42) 36 | // 37 | // // Verify that the mock was called with the correct arguments 38 | // Verify(myMock, Times(1)).MyMethod(Any[string](), Any[int]()) 39 | // } 40 | func Mock[T any](ctrl *matchers.MockController) T { 41 | return registry.Mock[T](ctrl) 42 | } 43 | 44 | // Any returns a mock value of type T that matches any value of type T. 45 | // This can be useful when setting up mock behaviors for methods that take arguments of type T, 46 | // but the specific argument value is not important for the test case. 47 | // 48 | // Example usage: 49 | // 50 | // // Set up a mock behavior for a method that takes a string argument 51 | // WhenSingle(myMock.MyMethod(mock.Any[string]())).ThenReturn("bar") 52 | // 53 | // // Set up a mock behavior for a method that takes an integer argument 54 | // WhenSingle(myMock.MyOtherMethod(mock.Any[int]())).ThenReturn("baz") 55 | func Any[T any]() T { 56 | registry.AddMatcher(registry.AnyMatcher[T]()) 57 | var t T 58 | return t 59 | } 60 | 61 | // AnyInt is an alias for Any[int] 62 | // See Any for more description 63 | func AnyInt() int { 64 | return Any[int]() 65 | } 66 | 67 | // AnyString is an alias for Any[string] 68 | // See Any for more description 69 | func AnyString() string { 70 | return Any[string]() 71 | } 72 | 73 | // AnyInterface is an alias for Any[any] 74 | // See Any for more description 75 | func AnyInterface() any { 76 | return Any[any]() 77 | } 78 | 79 | // AnyContext is an alias for Any[context.Context] 80 | // See Any for more description 81 | func AnyContext() context.Context { 82 | return Any[context.Context]() 83 | } 84 | 85 | // AnyOfType is an alias for Any[T] for specific type 86 | // Used for automatic type inference 87 | func AnyOfType[T any](t T) T { 88 | return Any[T]() 89 | } 90 | 91 | // Nil returns matcher that matches nil argument. 92 | // Example usage: 93 | // 94 | // WhenSingle(myMock.MyMethod(Nil[string]())).ThenReturn("bar") 95 | func Nil[T any]() T { 96 | m := registry.FunMatcher[T]("Nil", func(m []any, actual T) bool { 97 | var d any = actual 98 | return d == nil 99 | }) 100 | registry.AddMatcher(m) 101 | var t T 102 | return t 103 | } 104 | 105 | // NotNil returns matcher that matches non-nil argument. 106 | // Example usage: 107 | // 108 | // WhenSingle(myMock.MyMethod(NotNil[string]())).ThenReturn("bar") 109 | func NotNil[T any]() T { 110 | m := registry.FunMatcher[T]("NotNil", func(m []any, actual T) bool { 111 | var d any = actual 112 | return d != nil 113 | }) 114 | registry.AddMatcher(m) 115 | var t T 116 | return t 117 | } 118 | 119 | // Regex returns matcher that matches string against provided pattern. 120 | // Example usage: 121 | // 122 | // WhenSingle(myMock.MyMethod(Regex[string]("foo"))).ThenReturn("bar") 123 | func Regex(pattern string) string { 124 | re, err := regexp.Compile(pattern) 125 | desc := fmt.Sprintf("Regex(%v)", pattern) 126 | if err != nil { 127 | desc = fmt.Sprintf("InvalidRegex(%v)", pattern) 128 | } 129 | m := registry.FunMatcher(desc, func(m []any, actual string) bool { 130 | return err == nil && re.MatchString(actual) 131 | }) 132 | registry.AddMatcher(m) 133 | return "" 134 | } 135 | 136 | // Substring returns matcher that matches any string that contains specified substring. 137 | // Example usage: 138 | // 139 | // WhenSingle(myMock.MyMethod(Substring("foo"))).ThenReturn("bar") 140 | func Substring(value string) string { 141 | desc := fmt.Sprintf("Substring(%v)", value) 142 | m := registry.FunMatcher(desc, func(m []any, actual string) bool { 143 | return strings.Contains(actual, value) 144 | }) 145 | registry.AddMatcher(m) 146 | return "" 147 | } 148 | 149 | // SliceLen returns matcher that matches any slice of length n. 150 | // Example usage: 151 | // 152 | // WhenSingle(myMock.MyMethod(SliceLen(10))).ThenReturn("bar") 153 | func SliceLen[T any](value int) []T { 154 | desc := fmt.Sprintf("SliceLen(%v)", value) 155 | m := registry.FunMatcher(desc, func(m []any, actual []T) bool { 156 | return len(actual) == value 157 | }) 158 | registry.AddMatcher(m) 159 | var t []T 160 | return t 161 | } 162 | 163 | // MapLen returns matcher that matches any map of length n. 164 | // Example usage: 165 | // 166 | // WhenSingle(myMock.MyMethod(MapLen(10))).ThenReturn("bar") 167 | func MapLen[K comparable, V any](value int) map[K]V { 168 | desc := fmt.Sprintf("MapLen(%v)", value) 169 | m := registry.FunMatcher(desc, func(m []any, actual map[K]V) bool { 170 | return len(actual) == value 171 | }) 172 | registry.AddMatcher(m) 173 | var t map[K]V 174 | return t 175 | } 176 | 177 | // SliceContains returns matcher that matches any slice that contains specified values. 178 | // Example usage: 179 | // 180 | // WhenSingle(myMock.MyMethod(SliceContains("foo", "bar"))).ThenReturn("baz") 181 | func SliceContains[T any](values ...T) []T { 182 | desc := fmt.Sprintf("SliceContains(%v)", values) 183 | m := registry.FunMatcher(desc, func(m []any, actual []T) bool { 184 | amap := make(map[any]struct{}) 185 | for _, v := range actual { 186 | amap[v] = struct{}{} 187 | } 188 | for _, v := range values { 189 | _, ok := amap[v] 190 | if !ok { 191 | return false 192 | } 193 | } 194 | return true 195 | }) 196 | registry.AddMatcher(m) 197 | var t []T 198 | return t 199 | } 200 | 201 | // MapContains returns matcher that matches any map that contains specified keys. 202 | // Example usage: 203 | // 204 | // WhenSingle(myMock.MyMethod(MapContains("foo", "bar"))).ThenReturn("baz") 205 | func MapContains[K comparable, V any](values ...K) map[K]V { 206 | desc := fmt.Sprintf("MapContains(%v)", values) 207 | m := registry.FunMatcher(desc, func(m []any, actual map[K]V) bool { 208 | for _, v := range values { 209 | _, ok := actual[v] 210 | if !ok { 211 | return false 212 | } 213 | } 214 | return true 215 | }) 216 | registry.AddMatcher(m) 217 | var t map[K]V 218 | return t 219 | } 220 | 221 | // SliceEqualUnordered returns matcher that matches slice with same values without taking order of elements into account. 222 | // Example usage: 223 | // 224 | // WhenSingle(myMock.MyMethod(SliceEqualUnordered([]int{2,1}))).ThenReturn("baz") 225 | func SliceEqualUnordered[T any](values []T) []T { 226 | desc := fmt.Sprintf("EqualUnordered(%v)", values) 227 | vmap := make(map[any]struct{}) 228 | for _, v := range values { 229 | vmap[v] = struct{}{} 230 | } 231 | m := registry.FunMatcher(desc, func(m []any, actual []T) bool { 232 | if len(vmap) != len(values) { 233 | return false 234 | } 235 | if len(actual) != len(values) { 236 | return false 237 | } 238 | for _, v := range actual { 239 | _, ok := vmap[v] 240 | if !ok { 241 | return false 242 | } 243 | } 244 | return true 245 | }) 246 | registry.AddMatcher(m) 247 | var t []T 248 | return t 249 | } 250 | 251 | // Exact returns a matcher that matches values of type T that are equal to the provided value. 252 | // The value passed to Exact must be comparable with values of type T. 253 | // 254 | // Example usage: 255 | // 256 | // // Set up a mock behavior for a method that takes a string argument equal to "foo" 257 | // WhenSingle(myMock.MyMethod(Exact("foo"))).ThenReturn("bar") 258 | // 259 | // // Set up a mock behavior for a method that takes an integer argument equal to 42 260 | // WhenSingle(myMock.MyOtherMethod(Exact(42))).ThenReturn("baz") 261 | func Exact[T comparable](value T) T { 262 | desc := fmt.Sprintf("Exact(%v)", value) 263 | m := registry.FunMatcher(desc, func(m []any, actual T) bool { 264 | vrv := reflect.ValueOf(value) 265 | arv := reflect.ValueOf(actual) 266 | if vrv.Kind() == reflect.Struct && arv.Kind() == reflect.Struct { 267 | return vrv == arv 268 | } 269 | if !vrv.Comparable() { 270 | return false 271 | } 272 | if !arv.Comparable() { 273 | return false 274 | } 275 | return value == actual 276 | }) 277 | registry.AddMatcher(m) 278 | var t T 279 | return t 280 | } 281 | 282 | // Equal returns a matcher that matches values of type T that are equal via reflect.DeepEqual to the provided value. 283 | // The value passed to Equal must be of the exact same type as values of type T. 284 | // 285 | // Example usage: 286 | // 287 | // // Set up a mock behavior for a method that takes a string argument exactly equal to "foo" 288 | // WhenSingle(myMock.MyMethod(Equal("foo"))).ThenReturn("bar") 289 | // 290 | // // Set up a mock behavior for a method that takes an integer argument exactly equal to 42 291 | // WhenSingle(myMock.MyOtherMethod(Equal(42))).ThenReturn("baz") 292 | func Equal[T any](value T) T { 293 | desc := fmt.Sprintf("Equal(%v)", value) 294 | m := registry.FunMatcher[T](desc, func(m []any, actual T) bool { 295 | return reflect.DeepEqual(value, actual) 296 | }) 297 | registry.AddMatcher(m) 298 | var t T 299 | return t 300 | } 301 | 302 | // NotEqual returns a matcher that matches values of type T that are not equal via reflect.DeepEqual to the provided value. 303 | // The value passed to NotEqual must be of the exact same type as values of type T. 304 | // 305 | // Example usage: 306 | // 307 | // // Set up a mock behavior for a method that takes a string argument not equal to "foo" 308 | // WhenSingle(myMock.MyMethod(NotEqual("foo"))).ThenReturn("bar") 309 | // 310 | // // Set up a mock behavior for a method that takes an integer argument not equal to 42 311 | // WhenSingle(myMock.MyOtherMethod(NotEqual(42))).ThenReturn("baz") 312 | func NotEqual[T any](value T) T { 313 | desc := fmt.Sprintf("NotEqual(%v)", value) 314 | m := registry.FunMatcher[T](desc, func(m []any, actual T) bool { 315 | return !reflect.DeepEqual(value, actual) 316 | }) 317 | registry.AddMatcher(m) 318 | var t T 319 | return t 320 | } 321 | 322 | // OneOf returns a matcher that matches at least one of values of type T that are equal via reflect.DeepEqual to the provided value. 323 | // The value passed to OneOf must be of the exact same type as values of type T. 324 | // 325 | // Example usage: 326 | // 327 | // // Set up a mock behavior for a method that takes a string argument equal to either "foo" or "bar" 328 | // WhenSingle(myMock.MyMethod(OneOf("foo", "bar"))).ThenReturn("bar") 329 | // 330 | // // Set up a mock behavior for a method that takes an integer argument equal to either 41 or 42 331 | // WhenSingle(myMock.MyOtherMethod(OneOf(41, 42))).ThenReturn("baz") 332 | func OneOf[T any](values ...T) T { 333 | vs := make([]string, len(values)) 334 | for i := range values { 335 | vs[i] = fmt.Sprintf("%v", values[i]) 336 | } 337 | 338 | desc := fmt.Sprintf("OneOf(%s)", strings.Join(vs, ",")) 339 | m := registry.FunMatcher[T](desc, func(args []any, t T) bool { 340 | for i := range values { 341 | if reflect.DeepEqual(values[i], t) { 342 | return true 343 | } 344 | } 345 | return false 346 | }) 347 | registry.AddMatcher(m) 348 | var t T 349 | return t 350 | } 351 | 352 | // CreateMatcher returns a func that creates a custom matcher on invocation. 353 | func CreateMatcher[T any](description string, f func(allArgs []any, actual T) bool) func() T { 354 | return func() T { 355 | m := registry.FunMatcher[T](description, f) 356 | registry.AddMatcher(m) 357 | var t T 358 | return t 359 | } 360 | } 361 | 362 | // WhenSingle takes an argument of type T and returns a ReturnerSingle interface 363 | // that allows for specifying a return value for a method call that has that argument. 364 | // This function should be used for method that returns exactly one return value 365 | // It acts like When, but also provides additional type check on return value 366 | // For more than on value consider using WhenDouble or When 367 | func WhenSingle[T any](t T) matchers.ReturnerSingle[T] { 368 | return registry.ToReturnerSingle[T](registry.When()) 369 | } 370 | 371 | // WhenDouble takes an arguments of type A and B and returns a ReturnerDouble interface 372 | // that allows for specifying two return values for a method call that has that argument. 373 | // This function should be used for method that returns exactly two return values 374 | // It acts like When, but also provides additional type check on return values 375 | // For more multiple return values consider using When 376 | func WhenDouble[A any, B any](a A, b B) matchers.ReturnerDouble[A, B] { 377 | return registry.ToReturnerDouble[A, B](registry.When()) 378 | } 379 | 380 | // When sets up a method call expectation on a mocked object with a specified set of arguments 381 | // and returns a ReturnerAll object that allows specifying the return values or answer function 382 | // for the method call. Arguments can be any values, and the method call expectation is matched 383 | // based on the types and values of the arguments passed. If multiple expectations match the same 384 | // method call, the first matching expectation will be used. 385 | func When(args ...any) matchers.ReturnerAll { 386 | return registry.When() 387 | } 388 | 389 | // Captor returns an ArgumentCaptor, which can be used to capture arguments 390 | // passed to a mocked method. ArgumentCaptor is a generic type, which means 391 | // that the type of the arguments to be captured should be specified when 392 | // calling Captor. 393 | func Captor[T any]() matchers.ArgumentCaptor[T] { 394 | return registry.NewArgumentCaptor[T]() 395 | } 396 | 397 | // Verify checks if the method call on the provided mock object matches the expected verification conditions. 398 | // 399 | // It takes two arguments: the mock object to be verified and a method verifier. The method verifier defines the conditions 400 | // that should be matched during the verification process. If the verification passes, Verify returns the mock object. 401 | // If it fails, it reports an error. 402 | // 403 | // The method verifier can be created using one of the following functions: 404 | // 405 | // - AtLeastOnce() MethodVerifier: Matches if the method is called at least once. 406 | // 407 | // - Once() MethodVerifier: Matches if the method is called exactly once. 408 | // 409 | // - Times(n int) MethodVerifier: Matches if the method is called n times. 410 | // 411 | // - Never() MethodVerifier: Matches if the method is never called. 412 | // 413 | // The Verify function is typically used to assert that a method is called with the correct arguments and/or that it is 414 | // called the correct number of times during a unit test. 415 | // 416 | // package simple 417 | // 418 | // import ( 419 | // . "github.com/ovechkin-dm/mockio/v2/mock" 420 | // "testing" 421 | // ) 422 | // 423 | // type myInterface interface { 424 | // Foo(a int) int 425 | // } 426 | // 427 | // func TestSimple(t *testing.T) { 428 | // ctrl := NewMockController(t) 429 | // m := Mock[myInterface](ctrl) 430 | // WhenSingle(m.Foo(Any[int]())).ThenReturn(42) 431 | // _ = m.Foo(10) 432 | // Verify(m, AtLeastOnce()).Foo(10) 433 | // } 434 | func Verify[T any](t T, v matchers.MethodVerifier) T { 435 | registry.VerifyMethod(t, v) 436 | return t 437 | } 438 | 439 | // AtLeastOnce returns a MethodVerifier that verifies if the number of method calls 440 | // is greater than zero. It can be used to verify that a method has been called at least once. 441 | // 442 | // Example usage: 443 | // 444 | // ctrl := NewMockController(t) 445 | // mockObj := Mock[MyInterface](ctrl) 446 | // mockObj.MyMethod("arg1") 447 | // mockObj.MyMethod("arg2") 448 | // Verify(mockObj, AtLeastOnce()).MyMethod(Any[string]()) 449 | // 450 | // This verifies that the MyMethod function of mockObj was called at least once. 451 | func AtLeastOnce() matchers.MethodVerifier { 452 | return matchers.AtLeastOnce() 453 | } 454 | 455 | // Once returns a MethodVerifier that expects a method to be called exactly once. 456 | // If the method is not called, or called more than once, an error will be returned during verification. 457 | func Once() matchers.MethodVerifier { 458 | return matchers.Times(1) 459 | } 460 | 461 | // Times returns a MethodVerifier that verifies the number of times a method has been called. 462 | // It takes an integer 'n' as an argument, which specifies the expected number of method calls. 463 | // 464 | // Example usage: 465 | // 466 | // // Create mock controller 467 | // ctrl := NewMockController(t) 468 | // 469 | // // Create a mock object for testing 470 | // mockObj := Mock[MyInterface](ctrl) 471 | // 472 | // // Call a method on the mock object 473 | // mockObj.MyMethod() 474 | // 475 | // // Verify that MyMethod was called exactly once 476 | // Verify(mockObj, Times(1)).MyMethod() 477 | // 478 | // // Call the method again 479 | // mockObj.MyMethod() 480 | // 481 | // // Verify that MyMethod was called exactly twice 482 | // Verify(mockObj, Times(2)).MyMethod() 483 | // 484 | // If the number of method calls does not match the expected number of method calls, an error is returned. 485 | // The error message will indicate the expected and actual number of method calls. 486 | func Times(n int) matchers.MethodVerifier { 487 | return matchers.Times(n) 488 | } 489 | 490 | // Never returns a MethodVerifier that verifies that a method has never been called. 491 | // 492 | // Example usage: 493 | // 494 | // // Create mock controller 495 | // ctrl := NewMockController(t) 496 | // 497 | // // Create a mock object for testing 498 | // mockObj := Mock[MyInterface](ctrl) 499 | // 500 | // // Verify that MyMethod was never called 501 | // Verify(mockObj, Never()).MyMethod() 502 | // 503 | // // Call the method 504 | // mockObj.MyMethod() 505 | // 506 | // // Verify that MyMethod was called at least once 507 | // Verify(mockObj, AtLeastOnce()).MyMethod() 508 | func Never() matchers.MethodVerifier { 509 | return matchers.Times(0) 510 | } 511 | 512 | // VerifyNoMoreInteractions verifies that there are no more unverified interactions with the mock object. 513 | // For example if 514 | // Example usage: 515 | // 516 | // // Create mock controller 517 | // ctrl := NewMockController(t) 518 | // 519 | // // Create a mock object for testing 520 | // mockObj := Mock[MyInterface](ctrl) 521 | // 522 | // // Call the method 523 | // mockObj.MyMethod() 524 | // 525 | // // Verify that MyMethod was called exactly once 526 | // Verify(mockObj, Once()).MyMethod() 527 | // 528 | // // Verify that there are no more unverified interactions 529 | // VerifyNoMoreInteractions(mockObj) 530 | func VerifyNoMoreInteractions(value any) { 531 | registry.VerifyNoMoreInteractions(value) 532 | } 533 | 534 | func NewMockController(t matchers.ErrorReporter, opts ...config.Option) *matchers.MockController { 535 | return registry.NewMockController(t, opts...) 536 | } 537 | -------------------------------------------------------------------------------- /mockopts/options.go: -------------------------------------------------------------------------------- 1 | package mockopts 2 | 3 | import ( 4 | "github.com/ovechkin-dm/mockio/v2/config" 5 | ) 6 | 7 | // WithoutStackTrace enables stack trace printing for mock errors. 8 | // By default, stack trace is being printed. 9 | // This option is useful for debugging. 10 | // Example: 11 | // 12 | // ctrl := NewMockController(t, mockopts.WithoutStackTrace()) 13 | func WithoutStackTrace() config.Option { 14 | return func(cfg *config.MockConfig) { 15 | cfg.PrintStackTrace = false 16 | } 17 | } 18 | 19 | // StrictVerify enables strict verification of mock calls. 20 | // This means that all mocked methods that are not called will be reported as errors, 21 | // and all not mocked methods that are called will be reported as errors. 22 | // By default, strict verification is disabled. 23 | func StrictVerify() config.Option { 24 | return func(cfg *config.MockConfig) { 25 | cfg.StrictVerify = true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /registry/captor.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | ) 7 | 8 | type recordable interface { 9 | Record(call *MethodCall, value any) 10 | RemoveRecord(call *MethodCall) 11 | } 12 | 13 | type capturedValue[T any] struct { 14 | value T 15 | call *MethodCall 16 | } 17 | 18 | type captorImpl[T any] struct { 19 | values []*capturedValue[T] 20 | ctx *mockContext 21 | lock sync.Mutex 22 | reporter *EnrichedReporter 23 | } 24 | 25 | func (c *captorImpl[T]) Capture() T { 26 | AddCaptor[T](c) 27 | var t T 28 | return t 29 | } 30 | 31 | func (c *captorImpl[T]) Last() T { 32 | values := c.Values() 33 | if len(values) == 0 { 34 | c.reporter.ReportEmptyCaptor() 35 | var t T 36 | return t 37 | } 38 | return values[len(values)-1] 39 | } 40 | 41 | func (c *captorImpl[T]) Values() []T { 42 | c.lock.Lock() 43 | defer c.lock.Unlock() 44 | result := make([]T, len(c.values)) 45 | for i := range c.values { 46 | result[i] = c.values[i].value 47 | } 48 | return result 49 | } 50 | 51 | func (c *captorImpl[T]) Record(call *MethodCall, value any) { 52 | c.lock.Lock() 53 | defer c.lock.Unlock() 54 | t, ok := value.(T) 55 | if !ok { 56 | tp := reflect.TypeOf(new(T)).Elem() 57 | c.reporter.ReportInvalidCaptorValue(tp, reflect.TypeOf(value)) 58 | return 59 | } 60 | cv := &capturedValue[T]{ 61 | value: t, 62 | call: call, 63 | } 64 | c.values = append(c.values, cv) 65 | } 66 | 67 | func (c *captorImpl[T]) RemoveRecord(call *MethodCall) { 68 | c.lock.Lock() 69 | defer c.lock.Unlock() 70 | wo := make([]*capturedValue[T], 0) 71 | for _, v := range c.values { 72 | if v.call != call { 73 | wo = append(wo, v) 74 | } 75 | } 76 | c.values = wo 77 | } 78 | -------------------------------------------------------------------------------- /registry/handler.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "sync" 7 | 8 | "github.com/ovechkin-dm/mockio/v2/matchers" 9 | ) 10 | 11 | type invocationHandler struct { 12 | ctx *mockContext 13 | methods map[string]*methodRecorder 14 | instanceType reflect.Type 15 | lock sync.Mutex 16 | env *matchers.MockEnv 17 | reporter *EnrichedReporter 18 | } 19 | 20 | func (h *invocationHandler) Handle(method reflect.Method, values []reflect.Value) []reflect.Value { 21 | h.lock.Lock() 22 | defer h.lock.Unlock() 23 | values = h.refineValues(method, values) 24 | call := &MethodCall{ 25 | Method: method, 26 | Values: values, 27 | StackTrace: NewStackTrace(), 28 | } 29 | if h.ctx.getState().verifyState { 30 | return h.DoVerifyMethod(call) 31 | } 32 | h.methods[method.Name].calls = append(h.methods[method.Name].calls, call) 33 | return h.DoAnswer(call) 34 | } 35 | 36 | func (h *invocationHandler) DoAnswer(c *MethodCall) []reflect.Value { 37 | rec := h.methods[c.Method.Name] 38 | h.ctx.getState().whenHandler = h 39 | h.ctx.getState().whenCall = c 40 | var matched bool 41 | for _, mm := range rec.methodMatches { 42 | matched = true 43 | if len(mm.matchers) != len(c.Values) { 44 | continue 45 | } 46 | for argIdx, matcher := range mm.matchers { 47 | if !matcher.matcher.Match(valueSliceToInterfaceSlice(c.Values), valueToInterface(c.Values[argIdx])) { 48 | matched = false 49 | break 50 | } 51 | } 52 | if matched { 53 | ifaces := valueSliceToInterfaceSlice(c.Values) 54 | 55 | for i, m := range mm.matchers { 56 | if m.rec != nil { 57 | m.rec.Record(c, ifaces[i]) 58 | } 59 | } 60 | 61 | ansWrapper := mm.popAnswer() 62 | if ansWrapper == nil { 63 | return createDefaultReturnValues(c.Method) 64 | } 65 | 66 | retValues := ansWrapper.ans(ifaces) 67 | 68 | h.ctx.getState().whenAnswer = ansWrapper 69 | h.ctx.getState().whenMethodMatch = mm 70 | 71 | if !h.validateReturnValues(retValues, c.Method) { 72 | h.reporter.ReportInvalidReturnValues(h.instanceType, c.Method, retValues) 73 | return createDefaultReturnValues(c.Method) 74 | } 75 | 76 | result := interfaceSliceToValueSlice(retValues, c.Method) 77 | return result 78 | } 79 | } 80 | return createDefaultReturnValues(c.Method) 81 | } 82 | 83 | func (h *invocationHandler) When() matchers.ReturnerAll { 84 | h.lock.Lock() 85 | defer h.lock.Unlock() 86 | 87 | whenCall := h.ctx.getState().whenCall 88 | whenAnswer := h.ctx.getState().whenAnswer 89 | whenMethodMatch := h.ctx.getState().whenMethodMatch 90 | 91 | if whenCall == nil { 92 | h.reporter.ReportIncorrectWhenUsage() 93 | return nil 94 | } 95 | whenCall.WhenCall = true 96 | 97 | if whenMethodMatch != nil { 98 | for _, m := range whenMethodMatch.matchers { 99 | if m.rec != nil { 100 | m.rec.RemoveRecord(whenCall) 101 | } 102 | } 103 | } 104 | 105 | h.ctx.getState().whenHandler = nil 106 | h.ctx.getState().whenCall = nil 107 | h.ctx.getState().whenMethodMatch = nil 108 | h.ctx.getState().whenAnswer = nil 109 | 110 | if whenAnswer != nil && whenMethodMatch != nil { 111 | whenMethodMatch.putBackAnswer(whenAnswer) 112 | } 113 | 114 | if !h.validateMatchers(whenCall) { 115 | return NewEmptyReturner() 116 | } 117 | 118 | rec := h.methods[whenCall.Method.Name] 119 | 120 | argMatchers := h.ctx.getState().matchers 121 | 122 | h.ctx.getState().matchers = make([]*matcherWrapper, 0) 123 | m := &methodMatch{ 124 | matchers: argMatchers, 125 | unanswered: make([]*answerWrapper, 0), 126 | answered: make([]*answerWrapper, 0), 127 | stackTrace: NewStackTrace(), 128 | } 129 | rec.methodMatches = append(rec.methodMatches, m) 130 | return NewReturnerAll(h.ctx, m) 131 | } 132 | 133 | func (h *invocationHandler) VerifyMethod(verifier matchers.MethodVerifier) { 134 | h.lock.Lock() 135 | defer h.lock.Unlock() 136 | h.ctx.getState().verifyState = true 137 | h.ctx.getState().methodVerifier = verifier 138 | if len(h.ctx.getState().matchers) != 0 { 139 | h.reporter.ReportUnexpectedMatcherDeclaration(h.ctx.getState().matchers) 140 | } 141 | } 142 | 143 | func (h *invocationHandler) DoVerifyMethod(call *MethodCall) []reflect.Value { 144 | matchersOk := h.validateMatchers(call) 145 | argMatchers := h.ctx.getState().matchers 146 | 147 | h.ctx.getState().matchers = make([]*matcherWrapper, 0) 148 | h.ctx.getState().verifyState = false 149 | 150 | if !matchersOk { 151 | return createDefaultReturnValues(call.Method) 152 | } 153 | 154 | rec := h.methods[call.Method.Name] 155 | matchedInvocations := make([]*MethodCall, 0) 156 | for _, c := range rec.calls { 157 | if c.WhenCall { 158 | continue 159 | } 160 | matches := true 161 | if c.Method.Type != call.Method.Type { 162 | continue 163 | } 164 | if len(argMatchers) != len(c.Values) { 165 | continue 166 | } 167 | 168 | for i := range argMatchers { 169 | if !argMatchers[i].matcher.Match(valueSliceToInterfaceSlice(c.Values), valueToInterface(c.Values[i])) { 170 | matches = false 171 | break 172 | } 173 | } 174 | 175 | if matches { 176 | c.Verified = true 177 | matchedInvocations = append(matchedInvocations, c) 178 | } 179 | } 180 | verifyData := &matchers.MethodVerificationData{ 181 | NumMethodCalls: len(matchedInvocations), 182 | } 183 | err := h.ctx.getState().methodVerifier.Verify(verifyData) 184 | h.ctx.getState().methodVerifier = nil 185 | if err != nil { 186 | h.reporter.ReportVerifyMethodError( 187 | true, 188 | h.instanceType, 189 | call.Method, 190 | matchedInvocations, 191 | argMatchers, 192 | h.methods[call.Method.Name], 193 | err, 194 | nil, 195 | ) 196 | } 197 | for i, m := range argMatchers { 198 | if m.rec != nil { 199 | for _, inv := range matchedInvocations { 200 | argMatchers[i].rec.Record(inv, valueToInterface(inv.Values[i])) 201 | } 202 | } 203 | } 204 | return createDefaultReturnValues(call.Method) 205 | } 206 | 207 | // newHandler creates a new invocationHandler. 208 | // The `tp` parameter represents the reflector type for the target interface. 209 | func newHandler(tp reflect.Type, holder *mockContext, env *matchers.MockEnv) *invocationHandler { 210 | recorders := make(map[string]*methodRecorder) 211 | for i := 0; i < tp.NumMethod(); i++ { 212 | recorders[tp.Method(i).Name] = &methodRecorder{ 213 | methodMatches: make([]*methodMatch, 0), 214 | calls: make([]*MethodCall, 0), 215 | methodType: tp.Method(i), 216 | } 217 | } 218 | return newInvocationHandler(holder, recorders, tp, env) 219 | } 220 | 221 | func (h *invocationHandler) validateMatchers(call *MethodCall) bool { 222 | argMatchers := h.ctx.getState().matchers 223 | if len(argMatchers) == 0 { 224 | ifaces := valueSliceToInterfaceSlice(call.Values) 225 | for _, v := range ifaces { 226 | cur := v 227 | desc := fmt.Sprintf("Equal(%v)", v) 228 | fm := FunMatcher(desc, func(call []any, a any) bool { 229 | return reflect.DeepEqual(cur, a) 230 | }) 231 | mw := &matcherWrapper{ 232 | matcher: fm, 233 | rec: nil, 234 | } 235 | argMatchers = append(argMatchers, mw) 236 | } 237 | h.ctx.getState().matchers = argMatchers 238 | } 239 | if len(argMatchers) != len(call.Values) { 240 | h.reporter.ReportInvalidUseOfMatchers(h.instanceType, call, argMatchers) 241 | return false 242 | } 243 | return true 244 | } 245 | 246 | func (h *invocationHandler) validateReturnValues(result []any, method reflect.Method) bool { 247 | if method.Type.NumOut() != len(result) { 248 | return false 249 | } 250 | for i := range result { 251 | if result[i] == nil { 252 | continue 253 | } 254 | retExpected := method.Type.Out(i) 255 | retActual := reflect.TypeOf(result[i]) 256 | if retActual == nil { 257 | return false 258 | } 259 | if !retActual.AssignableTo(retExpected) { 260 | return false 261 | } 262 | } 263 | return true 264 | } 265 | 266 | func (h *invocationHandler) VerifyNoMoreInteractions(tearDown bool) { 267 | h.PostponedVerify(tearDown) 268 | unexpected := make([]*MethodCall, 0) 269 | for _, rec := range h.methods { 270 | for _, call := range rec.calls { 271 | if call.WhenCall { 272 | continue 273 | } 274 | if !call.Verified { 275 | unexpected = append(unexpected, call) 276 | } 277 | } 278 | } 279 | reportFatal := !tearDown 280 | if len(unexpected) > 0 { 281 | h.reporter.ReportNoMoreInteractionsExpected(reportFatal, h.instanceType, unexpected) 282 | } 283 | } 284 | 285 | func (h *invocationHandler) refineValues(method reflect.Method, values []reflect.Value) []reflect.Value { 286 | tp := method.Type 287 | if tp.IsVariadic() { 288 | result := make([]reflect.Value, 0) 289 | for i := 0; i < tp.NumIn()-1; i++ { 290 | result = append(result, values[i]) 291 | } 292 | last := values[len(values)-1] 293 | for i := 0; i < last.Len(); i++ { 294 | result = append(result, last.Index(i)) 295 | } 296 | return result 297 | } 298 | return values 299 | } 300 | 301 | func (h *invocationHandler) PostponedVerify(tearDown bool) { 302 | for _, rec := range h.methods { 303 | for _, match := range rec.methodMatches { 304 | if len(match.verifiers) == 0 { 305 | continue 306 | } 307 | matchedInvocations := make([]*MethodCall, 0) 308 | for _, call := range rec.calls { 309 | if call.WhenCall { 310 | continue 311 | } 312 | matches := true 313 | for i := range match.matchers { 314 | if !match.matchers[i].matcher.Match(valueSliceToInterfaceSlice(call.Values), valueToInterface(call.Values[i])) { 315 | matches = false 316 | break 317 | } 318 | } 319 | if matches { 320 | call.Verified = true 321 | matchedInvocations = append(matchedInvocations, call) 322 | } 323 | } 324 | verifyData := &matchers.MethodVerificationData{ 325 | NumMethodCalls: len(matchedInvocations), 326 | } 327 | for _, v := range match.verifiers { 328 | err := v.Verify(verifyData) 329 | if err != nil { 330 | var stackTrace *StackTrace 331 | if tearDown { 332 | stackTrace = match.stackTrace 333 | } 334 | h.reporter.ReportVerifyMethodError( 335 | !tearDown, 336 | h.instanceType, 337 | rec.methodType, 338 | matchedInvocations, 339 | match.matchers, 340 | rec, 341 | err, 342 | stackTrace, 343 | ) 344 | } 345 | } 346 | } 347 | } 348 | } 349 | 350 | func (h *invocationHandler) TearDown() { 351 | if h.env.Config.StrictVerify { 352 | for _, m := range h.methods { 353 | for _, mm := range m.methodMatches { 354 | if len(mm.verifiers) == 0 { 355 | mm.verifiers = append(mm.verifiers, matchers.AtLeastOnce()) 356 | } 357 | } 358 | } 359 | h.VerifyNoMoreInteractions(true) 360 | } else { 361 | h.PostponedVerify(true) 362 | } 363 | } 364 | 365 | func newInvocationHandler( 366 | ctx *mockContext, 367 | methods map[string]*methodRecorder, 368 | instanceType reflect.Type, 369 | env *matchers.MockEnv, 370 | ) *invocationHandler { 371 | handler := &invocationHandler{ 372 | ctx: ctx, 373 | methods: methods, 374 | instanceType: instanceType, 375 | lock: sync.Mutex{}, 376 | env: env, 377 | reporter: newEnrichedReporter(env.Reporter, env.Config), 378 | } 379 | return handler 380 | } 381 | -------------------------------------------------------------------------------- /registry/matchers.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/ovechkin-dm/mockio/v2/matchers" 8 | ) 9 | 10 | func AnyMatcher[T any]() matchers.Matcher[T] { 11 | return &matcherImpl[T]{ 12 | f: func(values []any, a T) bool { 13 | return true 14 | }, 15 | desc: fmt.Sprintf("Any[%s]", reflect.TypeOf(new(T)).Elem().String()), 16 | } 17 | } 18 | 19 | func FunMatcher[T any](description string, f func([]any, T) bool) matchers.Matcher[T] { 20 | return &matcherImpl[T]{ 21 | f: f, 22 | desc: description, 23 | } 24 | } 25 | 26 | type matcherImpl[T any] struct { 27 | f func([]any, T) bool 28 | desc string 29 | } 30 | 31 | func (m *matcherImpl[T]) Description() string { 32 | return m.desc 33 | } 34 | 35 | func (m *matcherImpl[T]) Match(allArgs []any, actual T) bool { 36 | return m.f(allArgs, actual) 37 | } 38 | 39 | func untypedMatcher[T any](src matchers.Matcher[T]) matchers.Matcher[any] { 40 | return &matcherImpl[any]{ 41 | f: func(args []any, a any) bool { 42 | var casted T 43 | if a == nil { 44 | return src.Match(args, casted) 45 | } 46 | c, ok := a.(T) 47 | if !ok { 48 | return false 49 | } 50 | casted = c 51 | return src.Match(args, casted) 52 | }, 53 | desc: src.Description(), 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /registry/registry.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "sync" 7 | 8 | "github.com/ovechkin-dm/go-dyno/pkg/dyno" 9 | "github.com/ovechkin-dm/go-dyno/pkg/dynoopts" 10 | 11 | "github.com/ovechkin-dm/mockio/v2/config" 12 | "github.com/ovechkin-dm/mockio/v2/matchers" 13 | "github.com/ovechkin-dm/mockio/v2/threadlocal" 14 | ) 15 | 16 | type HandlerHolder interface { 17 | Handler() matchers.Handler 18 | } 19 | 20 | var instance = threadlocal.NewThreadLocal(newRegistry) 21 | 22 | type Registry struct { 23 | mockContext *mockContext 24 | reporter *EnrichedReporter 25 | } 26 | 27 | func getInstance() *Registry { 28 | v := instance.Get() 29 | if v == nil { 30 | v = newRegistry() 31 | v.mockContext = newMockContext() 32 | instance.Set(v) 33 | } 34 | return v 35 | } 36 | 37 | func Mock[T any](ctrl *matchers.MockController) T { 38 | tp := reflect.TypeOf(new(T)).Elem() 39 | handler := ctrl.MockFactory.BuildHandler(ctrl.Env, tp) 40 | t, err := dyno.DynamicByType(handler.Handle, tp, dynoopts.WithPayload(handler)) 41 | if err != nil { 42 | getInstance().reporter.FailNow(fmt.Errorf("error creating mock: %w", err)) 43 | var zero T 44 | return zero 45 | } 46 | result, ok := t.(T) 47 | if !ok { 48 | getInstance().reporter.FailNow(fmt.Errorf("error casting mock to type %s", tp.String())) 49 | var zero T 50 | return zero 51 | } 52 | return result 53 | } 54 | 55 | func AddMatcher[T any](m matchers.Matcher[T]) { 56 | w := &matcherWrapper{ 57 | matcher: untypedMatcher(m), 58 | rec: nil, 59 | stackTrace: NewStackTrace(), 60 | } 61 | getInstance().mockContext.getState().matchers = append(getInstance().mockContext.getState().matchers, w) 62 | } 63 | 64 | func AddCaptor[T any](c *captorImpl[T]) { 65 | tp := reflect.TypeOf(new(T)).Elem() 66 | w := &matcherWrapper{ 67 | matcher: FunMatcher(fmt.Sprintf("Captor[%s]", tp), func(call []any, a any) bool { 68 | return true 69 | }), 70 | rec: c, 71 | stackTrace: NewStackTrace(), 72 | } 73 | getInstance().mockContext.getState().matchers = append(getInstance().mockContext.getState().matchers, w) 74 | } 75 | 76 | func When() matchers.ReturnerAll { 77 | wh := getInstance().mockContext.getState().whenHandler 78 | if wh == nil { 79 | getInstance().reporter.ReportIncorrectWhenUsage() 80 | return nil 81 | } 82 | return wh.When() 83 | } 84 | 85 | func VerifyMethod(t any, v matchers.MethodVerifier) { 86 | handler := UnwrapHandler(t) 87 | if handler == nil { 88 | return 89 | } 90 | handler.VerifyMethod(v) 91 | } 92 | 93 | func VerifyNoMoreInteractions(t any) { 94 | handler := UnwrapHandler(t) 95 | if handler == nil { 96 | return 97 | } 98 | handler.VerifyNoMoreInteractions(false) 99 | } 100 | 101 | func newRegistry() *Registry { 102 | cfg := &config.MockConfig{ 103 | PrintStackTrace: false, 104 | } 105 | reporter := newEnrichedReporter(&panicReporter{}, cfg) 106 | return &Registry{ 107 | mockContext: newMockContext(), 108 | reporter: reporter, 109 | } 110 | } 111 | 112 | func NewArgumentCaptor[T any]() matchers.ArgumentCaptor[T] { 113 | return &captorImpl[T]{ 114 | values: make([]*capturedValue[T], 0), 115 | ctx: getInstance().mockContext, 116 | lock: sync.Mutex{}, 117 | reporter: getInstance().reporter, 118 | } 119 | } 120 | 121 | func NewMockController(reporter matchers.ErrorReporter, opts ...config.Option) *matchers.MockController { 122 | cfg := config.NewConfig() 123 | if reporter == nil { 124 | panic("MockController: provided a nil error reporting (*testing.T) instance") 125 | } 126 | for _, opt := range opts { 127 | opt(cfg) 128 | } 129 | env := &matchers.MockEnv{ 130 | Reporter: reporter, 131 | Config: cfg, 132 | } 133 | factory := &mockFactoryImpl{} 134 | ctrl := &matchers.MockController{ 135 | Env: env, 136 | MockFactory: factory, 137 | } 138 | return ctrl 139 | } 140 | 141 | func UnwrapHandler(mock any) *invocationHandler { 142 | if mock == nil { 143 | getInstance().reporter.ReportUnregisteredMockVerify(mock) 144 | } 145 | handlerHolder, ok := mock.(HandlerHolder) 146 | var handler *invocationHandler 147 | if ok { 148 | handler, handlerOk := handlerHolder.Handler().(*invocationHandler) 149 | if handlerOk { 150 | return handler 151 | } 152 | } 153 | payload, err := dyno.UnwrapPayload(mock) 154 | if err != nil { 155 | getInstance().reporter.ReportUnregisteredMockVerify(mock) 156 | return nil 157 | } 158 | handler, ok = payload.(*invocationHandler) 159 | if !ok { 160 | getInstance().reporter.ReportUnregisteredMockVerify(mock) 161 | return nil 162 | } 163 | return handler 164 | } 165 | 166 | type mockFactoryImpl struct{} 167 | 168 | func (m *mockFactoryImpl) BuildHandler(env *matchers.MockEnv, ifaceType reflect.Type) matchers.Handler { 169 | handler := newHandler(ifaceType, getInstance().mockContext, env) 170 | env.Reporter.Cleanup(handler.TearDown) 171 | return handler 172 | } 173 | -------------------------------------------------------------------------------- /registry/reporter.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | 8 | "github.com/ovechkin-dm/mockio/v2/config" 9 | "github.com/ovechkin-dm/mockio/v2/matchers" 10 | ) 11 | 12 | type panicReporter struct{} 13 | 14 | func (p *panicReporter) Cleanup(f func()) { 15 | } 16 | 17 | func (p *panicReporter) Fatalf(format string, args ...any) { 18 | panic(fmt.Sprintf(format, args...)) 19 | } 20 | 21 | func (p *panicReporter) Errorf(format string, args ...any) { 22 | panic(fmt.Sprintf(format, args...)) 23 | } 24 | 25 | type EnrichedReporter struct { 26 | reporter matchers.ErrorReporter 27 | cfg *config.MockConfig 28 | } 29 | 30 | func (e *EnrichedReporter) Errorf(format string, args ...any) { 31 | e.reporter.Errorf(format, args...) 32 | } 33 | 34 | func (e *EnrichedReporter) StackTraceFatalf(format string, args ...any) { 35 | e.StackTraceErrorf(nil, true, format, args...) 36 | } 37 | 38 | func (e *EnrichedReporter) StackTraceErrorf(s *StackTrace, fatal bool, format string, args ...any) { 39 | if s == nil { 40 | s = NewStackTrace() 41 | } 42 | result := fmt.Sprintf(format, args...) 43 | var st string 44 | if e.cfg.PrintStackTrace { 45 | st = fmt.Sprintf(`At: 46 | %s 47 | Cause: 48 | %s 49 | Trace: 50 | %s 51 | `, s.CallerLine(), result, s.WithoutLibraryCalls().String()) 52 | } else { 53 | st = fmt.Sprintf(`At: 54 | %s 55 | Cause: 56 | %s 57 | `, s.CallerLine(), result) 58 | } 59 | if fatal { 60 | e.Fatalf(st) 61 | } else { 62 | e.reporter.Errorf(st) 63 | } 64 | } 65 | 66 | func (e *EnrichedReporter) FailNow(err error) { 67 | e.Fatalf(err.Error()) 68 | } 69 | 70 | func (e *EnrichedReporter) Fatal(format string) { 71 | e.Fatalf(format) 72 | } 73 | 74 | func (e *EnrichedReporter) Fatalf(format string, args ...any) { 75 | e.reporter.Fatalf(format, args...) 76 | } 77 | 78 | func (e *EnrichedReporter) ReportIncorrectWhenUsage() { 79 | e.StackTraceFatalf(`When() requires an argument which has to be 'a method call on a mock'. 80 | For example: When(mock.GetArticles()).ThenReturn(articles)`) 81 | } 82 | 83 | func (e *EnrichedReporter) ReportUnregisteredMockVerify(t any) { 84 | e.StackTraceFatalf(`Argument passed to Verify() is %v and is not a mock, or a mock created in a different goroutine. 85 | Make sure you place the parenthesis correctly. 86 | Example of correct verification: 87 | Verify(mock, Times(10)).SomeMethod()`, t) 88 | } 89 | 90 | func (e *EnrichedReporter) ReportInvalidUseOfMatchers(instanceType reflect.Type, call *MethodCall, m []*matcherWrapper) { 91 | matcherArgs := make([]string, len(m)) 92 | for i := range m { 93 | matcherArgs[i] = m[i].matcher.Description() 94 | } 95 | matchersString := strings.Join(matcherArgs, ",") 96 | tp := call.Method.Type 97 | inArgs := make([]string, 0) 98 | methodSig := prettyPrintMethodSignature(instanceType, call.Method) 99 | for i := 0; i < tp.NumIn(); i++ { 100 | inArgs = append(inArgs, tp.In(i).String()) 101 | } 102 | inArgsStr := strings.Join(inArgs, ",") 103 | numExpected := call.Method.Type.NumIn() 104 | numActual := len(m) 105 | declarationLines := make([]string, 0) 106 | for i := range m { 107 | declarationLines = append(declarationLines, "\t\t"+m[i].stackTrace.CallerLine()) 108 | } 109 | decl := strings.Join(declarationLines, "\n") 110 | expectedStr := fmt.Sprintf("%v expected, %v recorded:\n", numExpected, numActual) 111 | if call.Method.Type.IsVariadic() { 112 | expectedStr = "" 113 | } 114 | e.StackTraceFatalf(`Invalid use of matchers 115 | %s%v 116 | method: 117 | %v 118 | expected: 119 | (%s) 120 | got: 121 | (%s) 122 | This can happen for 2 reasons: 123 | 1. Declaration of matcher outside When() call 124 | 2. Mixing matchers and exact values in When() call. Is this case, consider using "Exact" matcher.`, 125 | expectedStr, decl, methodSig, inArgsStr, matchersString) 126 | } 127 | 128 | func (e *EnrichedReporter) ReportVerifyMethodError( 129 | fatal bool, 130 | tp reflect.Type, 131 | method reflect.Method, 132 | invocations []*MethodCall, 133 | argMatchers []*matcherWrapper, 134 | recorder *methodRecorder, 135 | err error, 136 | stackTrace *StackTrace, 137 | ) { 138 | sb := strings.Builder{} 139 | for i, c := range invocations { 140 | if c.WhenCall { 141 | continue 142 | } 143 | sb.WriteString("\t\t" + c.StackTrace.CallerLine()) 144 | if i != len(invocations)-1 { 145 | sb.WriteString("\n") 146 | } 147 | } 148 | args := make([]string, len(argMatchers)) 149 | for i := range argMatchers { 150 | args[i] = argMatchers[i].matcher.Description() 151 | } 152 | callStr := PrettyPrintMethodInvocation(tp, method, args) 153 | 154 | other := strings.Builder{} 155 | for j, c := range recorder.calls { 156 | if c.WhenCall { 157 | continue 158 | } 159 | callArgs := make([]string, len(c.Values)) 160 | for i := range c.Values { 161 | callArgs[i] = fmt.Sprintf("%v", c.Values[i]) 162 | } 163 | pretty := PrettyPrintMethodInvocation(tp, c.Method, callArgs) 164 | other.WriteString(fmt.Sprintf("\t\t%s at %s", pretty, c.StackTrace.CallerLine())) 165 | if j != len(recorder.calls)-1 { 166 | other.WriteString("\n") 167 | } 168 | } 169 | if len(other.String()) == 0 && len(sb.String()) == 0 { 170 | e.StackTraceErrorf(stackTrace, fatal, `%v 171 | %v 172 | `, err, callStr) 173 | } else if len(invocations) == 0 { 174 | e.StackTraceErrorf(stackTrace, fatal, `%v 175 | %v 176 | However, there were other interactions with this method: 177 | %v`, err, callStr, other.String()) 178 | } else { 179 | e.StackTraceErrorf(stackTrace, fatal, `%v 180 | %v 181 | Invocations: 182 | %v`, err, callStr, sb.String()) 183 | } 184 | } 185 | 186 | func (e *EnrichedReporter) ReportEmptyCaptor() { 187 | e.StackTraceFatalf("no values were captured for captor") 188 | } 189 | 190 | func (e *EnrichedReporter) ReportInvalidCaptorValue(expectedType reflect.Type, actualType reflect.Type) { 191 | e.StackTraceFatalf("captor contains unexpected type") 192 | } 193 | 194 | func (e *EnrichedReporter) ReportInvalidReturnValues(instanceType reflect.Type, method reflect.Method, ret []any) { 195 | tp := method.Type 196 | outTypesSB := strings.Builder{} 197 | 198 | interfaceName := instanceType.Name() 199 | methodName := method.Name 200 | outTypesSB.WriteString(interfaceName + "." + methodName) 201 | outTypesSB.WriteString("(") 202 | for i := 0; i < tp.NumIn(); i++ { 203 | outTypesSB.WriteString(tp.In(i).Name()) 204 | if i != tp.NumIn()-1 { 205 | outTypesSB.WriteString(", ") 206 | } 207 | } 208 | outTypesSB.WriteString(")") 209 | if len(ret) > 0 { 210 | outTypesSB.WriteString(" ") 211 | } 212 | if len(ret) > 1 { 213 | outTypesSB.WriteString("(") 214 | } 215 | for i := 0; i < len(ret); i++ { 216 | of := reflect.ValueOf(ret[i]) 217 | if of.Kind() == reflect.Invalid { 218 | outTypesSB.WriteString("nil") 219 | } else { 220 | outTypesSB.WriteString(of.Type().String()) 221 | } 222 | 223 | if i != len(ret)-1 { 224 | outTypesSB.WriteString(", ") 225 | } 226 | } 227 | if len(ret) > 1 { 228 | outTypesSB.WriteString(")") 229 | } 230 | 231 | methodSig := prettyPrintMethodSignature(instanceType, method) 232 | 233 | e.StackTraceFatalf(`invalid return values 234 | expected: 235 | %v 236 | got: 237 | %s 238 | `, methodSig, outTypesSB.String()) 239 | } 240 | 241 | func newEnrichedReporter(reporter matchers.ErrorReporter, cfg *config.MockConfig) *EnrichedReporter { 242 | return &EnrichedReporter{ 243 | reporter: reporter, 244 | cfg: cfg, 245 | } 246 | } 247 | 248 | func prettyPrintMethodSignature(interfaceType reflect.Type, method reflect.Method) string { 249 | var signature string 250 | 251 | interfaceName := interfaceType.Name() 252 | methodName := method.Name 253 | methodType := method.Type 254 | signature += interfaceName + "." + methodName 255 | 256 | numParams := methodType.NumIn() 257 | signature += "(" 258 | for i := 0; i < numParams; i++ { 259 | paramType := methodType.In(i) 260 | signature += paramType.String() 261 | if i != numParams-1 { 262 | signature += ", " 263 | } 264 | } 265 | signature += ")" 266 | 267 | numReturns := methodType.NumOut() 268 | if numReturns > 0 { 269 | signature += " " 270 | } 271 | if numReturns > 1 { 272 | signature += "(" 273 | } 274 | for i := 0; i < numReturns; i++ { 275 | returnType := methodType.Out(i) 276 | signature += returnType.String() 277 | if i != numReturns-1 { 278 | signature += ", " 279 | } 280 | } 281 | if numReturns > 1 { 282 | signature += ")" 283 | } 284 | 285 | return signature 286 | } 287 | 288 | func PrettyPrintMethodInvocation(interfaceType reflect.Type, method reflect.Method, args []string) string { 289 | sb := strings.Builder{} 290 | interfaceName := interfaceType.Name() 291 | methodName := method.Name 292 | sb.WriteString(interfaceName + "." + methodName) 293 | sb.WriteRune('(') 294 | for i, v := range args { 295 | sb.WriteString(fmt.Sprintf("%v", v)) 296 | if i != len(args)-1 { 297 | sb.WriteString(", ") 298 | } 299 | } 300 | sb.WriteRune(')') 301 | return sb.String() 302 | } 303 | 304 | func (e *EnrichedReporter) ReportNoMoreInteractionsExpected(fatal bool, instanceType reflect.Type, calls []*MethodCall) { 305 | sb := strings.Builder{} 306 | for i, c := range calls { 307 | args := make([]string, 0) 308 | for _, v := range c.Values { 309 | args = append(args, fmt.Sprintf("%v", v)) 310 | } 311 | s := PrettyPrintMethodInvocation(instanceType, c.Method, args) 312 | line := fmt.Sprintf("\t\t%s at %s", s, c.StackTrace.CallerLine()) 313 | sb.WriteString(line) 314 | if i != len(calls)-1 { 315 | sb.WriteString("\n") 316 | } 317 | 318 | } 319 | e.StackTraceErrorf(nil, fatal, `No more interactions expected, but unverified interactions found: 320 | %v`, sb.String()) 321 | } 322 | 323 | func (e *EnrichedReporter) ReportUnexpectedMatcherDeclaration(m []*matcherWrapper) { 324 | sb := strings.Builder{} 325 | for i, v := range m { 326 | sb.WriteString("\t\tat " + v.stackTrace.CallerLine()) 327 | if i != len(m)-1 { 328 | sb.WriteString("\n") 329 | } 330 | } 331 | e.StackTraceFatalf(`Unexpected matchers declaration. 332 | %s 333 | Matchers can only be used inside When() method call.`, sb.String()) 334 | } 335 | -------------------------------------------------------------------------------- /registry/returners.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "github.com/ovechkin-dm/mockio/v2/matchers" 5 | ) 6 | 7 | func ToReturnerSingle[T any](retAll matchers.ReturnerAll) matchers.ReturnerSingle[T] { 8 | return &returnerSingleImpl[T]{ 9 | all: retAll, 10 | } 11 | } 12 | 13 | func ToReturnerDouble[A any, B any](retAll matchers.ReturnerAll) matchers.ReturnerDouble[A, B] { 14 | return &returnerDoubleImpl[A, B]{ 15 | all: retAll, 16 | } 17 | } 18 | 19 | type returnerDummyImpl struct{} 20 | 21 | func (r *returnerDummyImpl) ThenReturn(values ...any) matchers.ReturnerAll { 22 | return r 23 | } 24 | 25 | func (r *returnerDummyImpl) ThenAnswer(f matchers.Answer) matchers.ReturnerAll { 26 | return r 27 | } 28 | 29 | func (r *returnerDummyImpl) Verify(m matchers.MethodVerifier) { 30 | } 31 | 32 | type returnerAllImpl struct { 33 | methodMatch *methodMatch 34 | ctx *mockContext 35 | } 36 | 37 | type returnerSingleImpl[T any] struct { 38 | all matchers.ReturnerAll 39 | } 40 | 41 | func (r *returnerSingleImpl[T]) ThenReturn(value T) matchers.ReturnerSingle[T] { 42 | return r.ThenAnswer(func(args []any) T { 43 | return value 44 | }) 45 | } 46 | 47 | func (r *returnerSingleImpl[T]) ThenAnswer(f func(args []any) T) matchers.ReturnerSingle[T] { 48 | all := r.all.ThenAnswer(func(args []any) []any { 49 | return []any{f(args)} 50 | }) 51 | return &returnerSingleImpl[T]{ 52 | all: all, 53 | } 54 | } 55 | 56 | func (r *returnerSingleImpl[T]) Verify(verifier matchers.MethodVerifier) { 57 | r.all.Verify(verifier) 58 | } 59 | 60 | type returnerDoubleImpl[A any, B any] struct { 61 | all matchers.ReturnerAll 62 | } 63 | 64 | func (r *returnerDoubleImpl[A, B]) ThenReturn(a A, b B) matchers.ReturnerDouble[A, B] { 65 | return r.ThenAnswer(func(args []any) (A, B) { 66 | return a, b 67 | }) 68 | } 69 | 70 | func (r *returnerDoubleImpl[A, B]) ThenAnswer(f func(args []any) (A, B)) matchers.ReturnerDouble[A, B] { 71 | all := r.all.ThenAnswer(func(args []any) []any { 72 | t, e := f(args) 73 | return []any{t, e} 74 | }) 75 | return &returnerDoubleImpl[A, B]{ 76 | all: all, 77 | } 78 | } 79 | 80 | func (r *returnerDoubleImpl[A, B]) Verify(verifier matchers.MethodVerifier) { 81 | r.all.Verify(verifier) 82 | } 83 | 84 | func (r *returnerAllImpl) ThenReturn(values ...any) matchers.ReturnerAll { 85 | return r.ThenAnswer(makeReturnFunc(values)) 86 | } 87 | 88 | func (r *returnerAllImpl) ThenAnswer(f matchers.Answer) matchers.ReturnerAll { 89 | wrapper := &answerWrapper{ 90 | ans: f, 91 | } 92 | r.methodMatch.addAnswer(wrapper) 93 | return r 94 | } 95 | 96 | func (r *returnerAllImpl) Verify(verifier matchers.MethodVerifier) { 97 | r.methodMatch.verifiers = append(r.methodMatch.verifiers, verifier) 98 | } 99 | 100 | func makeReturnFunc(values []any) matchers.Answer { 101 | return func(args []any) []interface{} { 102 | return values 103 | } 104 | } 105 | 106 | func NewReturnerAll(ctx *mockContext, data *methodMatch) matchers.ReturnerAll { 107 | return &returnerAllImpl{ 108 | methodMatch: data, 109 | ctx: ctx, 110 | } 111 | } 112 | 113 | func NewEmptyReturner() matchers.ReturnerAll { 114 | return &returnerDummyImpl{} 115 | } 116 | -------------------------------------------------------------------------------- /registry/state.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | "sync/atomic" 7 | 8 | "github.com/ovechkin-dm/mockio/v2/matchers" 9 | "github.com/ovechkin-dm/mockio/v2/threadlocal" 10 | ) 11 | 12 | type fiberState struct { 13 | matchers []*matcherWrapper 14 | whenHandler *invocationHandler 15 | verifyState bool 16 | methodVerifier matchers.MethodVerifier 17 | whenCall *MethodCall 18 | whenAnswer *answerWrapper 19 | whenMethodMatch *methodMatch 20 | } 21 | 22 | type mockContext struct { 23 | state threadlocal.ThreadLocal[*fiberState] 24 | lock sync.Mutex 25 | routineID int64 26 | } 27 | 28 | type methodRecorder struct { 29 | methodMatches []*methodMatch 30 | calls []*MethodCall 31 | methodType reflect.Method 32 | } 33 | 34 | type methodMatch struct { 35 | matchers []*matcherWrapper 36 | unanswered []*answerWrapper 37 | answered []*answerWrapper 38 | lock sync.Mutex 39 | lastAnswer *answerWrapper 40 | invocations int64 41 | verifiers []matchers.MethodVerifier 42 | stackTrace *StackTrace 43 | } 44 | 45 | func (m *methodMatch) popAnswer() *answerWrapper { 46 | m.lock.Lock() 47 | defer m.lock.Unlock() 48 | atomic.AddInt64(&m.invocations, 1) 49 | if len(m.unanswered) == 0 { 50 | return m.lastAnswer 51 | } 52 | last := m.unanswered[0] 53 | m.unanswered = m.unanswered[1:] 54 | m.answered = append(m.answered, last) 55 | m.lastAnswer = last 56 | return last 57 | } 58 | 59 | func (m *methodMatch) addAnswer(wrapper *answerWrapper) { 60 | m.lock.Lock() 61 | defer m.lock.Unlock() 62 | m.unanswered = append(m.unanswered, wrapper) 63 | } 64 | 65 | func (m *methodMatch) putBackAnswer(wrapper *answerWrapper) { 66 | m.lock.Lock() 67 | defer m.lock.Unlock() 68 | atomic.AddInt64(&m.invocations, -1) 69 | foundIdx := -1 70 | for i := len(m.answered) - 1; i >= 0; i-- { 71 | if wrapper == m.answered[i] { 72 | foundIdx = i 73 | break 74 | } 75 | } 76 | if foundIdx == -1 { 77 | return 78 | } 79 | for i := foundIdx; i < len(m.unanswered)-1; i++ { 80 | m.answered[i] = m.answered[i+1] 81 | } 82 | m.answered = m.answered[0 : len(m.answered)-1] 83 | m.unanswered = append(m.unanswered, wrapper) 84 | } 85 | 86 | type answerWrapper struct { 87 | ans matchers.Answer 88 | } 89 | 90 | type matcherWrapper struct { 91 | matcher matchers.Matcher[any] 92 | rec recordable 93 | stackTrace *StackTrace 94 | } 95 | 96 | func (ctx *mockContext) getState() *fiberState { 97 | return ctx.state.Get() 98 | } 99 | 100 | func newMockContext() *mockContext { 101 | return &mockContext{ 102 | state: threadlocal.NewThreadLocal(func() *fiberState { 103 | return &fiberState{ 104 | matchers: make([]*matcherWrapper, 0), 105 | whenHandler: nil, 106 | whenCall: nil, 107 | methodVerifier: nil, 108 | verifyState: false, 109 | } 110 | }), 111 | lock: sync.Mutex{}, 112 | routineID: threadlocal.GoId(), 113 | } 114 | } 115 | 116 | type MethodCall struct { 117 | Method reflect.Method 118 | Values []reflect.Value 119 | WhenCall bool 120 | Verified bool 121 | StackTrace *StackTrace 122 | } 123 | -------------------------------------------------------------------------------- /registry/util.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "runtime/debug" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | PackageName = "github.com/ovechkin-dm/mockio" 12 | DynoPackageName = "github.com/ovechkin-dm/go-dyno" 13 | TestPackageName = "github.com/ovechkin-dm/mockio/v2/tests" 14 | DebugPackage = "runtime/debug.Stack()" 15 | GOIDPackageName = "github.com/petermattis/goid" 16 | ) 17 | 18 | func createDefaultReturnValues(m reflect.Method) []reflect.Value { 19 | result := make([]reflect.Value, m.Type.NumOut()) 20 | for i := 0; i < m.Type.NumOut(); i++ { 21 | result[i] = reflect.New(m.Type.Out(i)).Elem() 22 | } 23 | return result 24 | } 25 | 26 | func valueSliceToInterfaceSlice(values []reflect.Value) []any { 27 | result := make([]any, len(values)) 28 | for i := range values { 29 | result[i] = valueToInterface(values[i]) 30 | } 31 | return result 32 | } 33 | 34 | func valueToInterface(value reflect.Value) any { 35 | return value.Interface() 36 | } 37 | 38 | func interfaceSliceToValueSlice(values []any, m reflect.Method) []reflect.Value { 39 | result := make([]reflect.Value, len(values)) 40 | for i := range values { 41 | retV := reflect.New(m.Type.Out(i)).Elem() 42 | if values[i] != nil { 43 | retV.Set(reflect.ValueOf(values[i])) 44 | } 45 | result[i] = retV 46 | } 47 | return result 48 | } 49 | 50 | type StackTrace struct { 51 | goroutine string 52 | lines []*StackLine 53 | } 54 | 55 | type StackLine struct { 56 | Path string 57 | Line string 58 | } 59 | 60 | func (s *StackLine) String() string { 61 | return fmt.Sprintf("%s\n\t%s", s.Path, s.Line) 62 | } 63 | 64 | func (s *StackLine) IsLibraryStackLine() bool { 65 | if strings.Contains(s.Path, DebugPackage) { 66 | return true 67 | } 68 | if strings.Contains(s.Path, DynoPackageName) { 69 | return true 70 | } 71 | if strings.Contains(s.Path, GOIDPackageName) { 72 | return true 73 | } 74 | return strings.Contains(s.Path, PackageName) && !strings.Contains(s.Path, TestPackageName) 75 | } 76 | 77 | func (s *StackTrace) String() string { 78 | result := make([]string, 0) 79 | for i := range s.lines { 80 | result = append(result, s.lines[i].String()+"\n") 81 | } 82 | return strings.Join(result, "") 83 | } 84 | 85 | func (s *StackTrace) CallerLine() string { 86 | for i := range s.lines { 87 | if s.lines[i].IsLibraryStackLine() { 88 | if i < len(s.lines)-1 && !s.lines[i+1].IsLibraryStackLine() { 89 | return s.lines[i+1].Line 90 | } 91 | } 92 | } 93 | return "" 94 | } 95 | 96 | func (s *StackTrace) WithoutLibraryCalls() *StackTrace { 97 | var result []*StackLine 98 | for i := range s.lines { 99 | if !s.lines[i].IsLibraryStackLine() { 100 | result = append(result, s.lines[i]) 101 | } 102 | } 103 | return &StackTrace{ 104 | lines: result, 105 | } 106 | } 107 | 108 | func NewStackTrace() *StackTrace { 109 | stack := string(debug.Stack()) 110 | lines := strings.Split(stack, "\n") 111 | goroutine := lines[0] 112 | stackLines := make([]*StackLine, 0) 113 | for i := 1; i < len(lines)-1; i += 2 { 114 | l := &StackLine{ 115 | Path: strings.TrimSpace(lines[i]), 116 | Line: strings.TrimSpace(lines[i+1]), 117 | } 118 | stackLines = append(stackLines, l) 119 | } 120 | return &StackTrace{ 121 | goroutine: goroutine, 122 | lines: stackLines, 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /templates/mockery.tmpl: -------------------------------------------------------------------------------- 1 | // Code generated by mockery; DO NOT EDIT. 2 | // github.com/vektra/mockery 3 | // template: mockio 4 | {{ $root := . }} 5 | 6 | package {{.PkgName}} 7 | 8 | {{- $matchersPackage := $root.Registry.AddImport "matchers" "github.com/ovechkin-dm/mockio/v2/matchers" }} 9 | {{- $reflectPackage := $root.Registry.AddImport "reflect" "reflect" }} 10 | 11 | import ( 12 | {{- range $_, $import := $root.Imports}} 13 | {{ $import.ImportStatement }} 14 | {{- end}} 15 | ) 16 | 17 | {{/* CREATE CONSTRUCTOR */}} 18 | 19 | {{- range $i, $mock := .Interfaces }} {{/* START MOCK RANGE */}} 20 | {{ $new := "New" }} 21 | {{ if firstIsLower .StructName }} 22 | {{ $new = "new" }} 23 | {{- end }} 24 | {{- $constructorName := printf "%s%s" $new (.StructName | firstUpper) }} 25 | {{- with $constructorScope := $root.Registry.MethodScope }} {{/* START CONSTRUCTOR */}} 26 | {{- $ctrlVar := $constructorScope.AllocateName "ctrl" }} 27 | {{- $tpVar := $constructorScope.AllocateName "tp" }} 28 | {{- $handlerVar := $constructorScope.AllocateName "handler" }} 29 | {{- $methodsMapVar := $constructorScope.AllocateName "methodsMap" }} 30 | {{- $iVar := $constructorScope.AllocateName "i" }} 31 | {{- $methodVar := $constructorScope.AllocateName "method" }} 32 | {{- $mockVar := $constructorScope.AllocateName "mockVar" }} 33 | 34 | 35 | func {{ $constructorName }}{{ $mock.TypeConstraint }} ({{ $ctrlVar }} *{{ $matchersPackage.Qualifier }}.MockController) {{ $root.SrcPkgQualifier }}{{ $mock.Name }}{{ $mock.TypeInstantiation }} { 36 | {{ $tpVar }} := {{ $reflectPackage.Qualifier }}.TypeOf(new({{ $root.SrcPkgQualifier }}{{ $mock.Name }}{{ $mock.TypeInstantiation }})).Elem() 37 | {{ $handlerVar }} := {{ $ctrlVar }}.MockFactory.BuildHandler(ctrl.Env, tp) 38 | {{ $methodsMapVar }} := make(map[string]{{ $reflectPackage.Qualifier }}.Method) 39 | for {{ $iVar }} := 0; {{ $iVar }} < {{ $tpVar }}.NumMethod(); {{ $iVar }}++ { 40 | {{ $methodVar }} := {{ $tpVar }}.Method({{ $iVar }}) 41 | {{ $methodsMapVar }}[{{ $methodVar }}.Name] = {{ $methodVar }} 42 | } 43 | {{ $mockVar }} := &{{ $mock.StructName }}{{ $mock.TypeInstantiation }}{ 44 | _handler: handler, 45 | _methodsMap: methodsMap, 46 | } 47 | 48 | return {{ $mockVar }} 49 | } 50 | 51 | {{- end }} {{/* END CONSTRUCTOR */}} 52 | 53 | 54 | // {{ $mock.StructName }} is an autogenerated mock type for the {{ $mock.Name }} type 55 | type {{ $mock.StructName }}{{ $mock.TypeConstraint }} struct { 56 | _handler {{ $matchersPackage.Qualifier }}.Handler 57 | _methodsMap map[string]{{ $reflectPackage.Qualifier }}.Method 58 | } 59 | 60 | {{- with $handlerMethodScope := $root.Registry.MethodScope }} {{/* START HANDLER METHOD */}} 61 | {{- $structHandlerMethodVar := $handlerMethodScope.AllocateName "mock" }} 62 | func ({{ $structHandlerMethodVar }} *{{$mock.StructName}}{{ $mock.TypeInstantiation }}) Handler() {{ $matchersPackage.Qualifier }}.Handler { 63 | return {{ $structHandlerMethodVar }}._handler 64 | } 65 | {{- end }} {{/* END HANDLER METHOD */}} 66 | 67 | {{/* RANGE OVER ALL METHODS */}} 68 | {{- range $methodIdx, $method := .Methods }} {{/* START METHOD RANGE */}} 69 | 70 | // {{ $method.Name }} provides a mock function for the type {{ $mock.StructName }} 71 | {{- $structVar := $method.Scope.AllocateName "mock" }} 72 | func ({{ $structVar }} *{{$mock.StructName}}{{ $mock.TypeInstantiation }}) {{$method.Name}}({{$method.ArgList}}) {{$method.ReturnArgTypeList}} { 73 | {{- $methodTypeVar := $method.Scope.AllocateName "methodType" }} 74 | {{- $argsVar := $method.Scope.AllocateName "args" }} 75 | {{- $outsVar := $method.Scope.AllocateName "outs" }} 76 | {{- $methodTypeVar }} := {{ $structVar }}._methodsMap["{{$method.Name}}"] 77 | {{ $argsVar }} := []{{ $reflectPackage.Qualifier }}.Value { 78 | {{- range $paramIdx, $param := $method.Params }} 79 | {{ $reflectPackage.Qualifier }}.ValueOf({{ $param.Name }}), 80 | {{- end }} 81 | } 82 | 83 | {{- if ne (len $method.Returns) 0}} 84 | outs := {{ $structVar }}._handler.Handle({{ $methodTypeVar }}, {{ $argsVar }}) 85 | 86 | {{- range $paramIdx, $param := $method.Returns }} 87 | {{- $retVarRaw := printf "r%d" $paramIdx }} 88 | {{- $retVar := $method.Scope.SuggestName $retVarRaw }} 89 | {{ $retVar }}, _ := outs[{{$paramIdx}}].Interface().({{ $param.TypeString }}) 90 | {{- end }} 91 | 92 | return {{ range $retIdx, $ret := $method.Returns }}{{- $retVarRaw := printf "r%d" $retIdx }}{{- $retVar := $method.Scope.SuggestName $retVarRaw }} {{ $retVar }} {{ if ne $retIdx (len $method.Returns | add -1) }}, {{ end }}{{ end }} 93 | {{- else }} 94 | _ = {{ $structVar }}._handler.Handle({{ $methodTypeVar }}, {{ $argsVar }}) 95 | {{- end }} 96 | } 97 | 98 | {{- end }} {{/* END METHOD RANGE */}} 99 | {{- end }} {{/* END MOCK RANGE */}} -------------------------------------------------------------------------------- /templates/mockery.tmpl.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "Mockio mockery template", 4 | "type": "object", 5 | "additionalProperties": false, 6 | "properties": {}, 7 | "required": [] 8 | } -------------------------------------------------------------------------------- /tests/captor/captor_test.go: -------------------------------------------------------------------------------- 1 | package captor 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ovechkin-dm/mockio/v2/tests/common" 7 | 8 | . "github.com/ovechkin-dm/mockio/v2/mock" 9 | ) 10 | 11 | type iface interface { 12 | Foo(i int) int 13 | VoidFoo(i int, j int) 14 | } 15 | 16 | func TestCaptorBasic(t *testing.T) { 17 | r := common.NewMockReporter(t) 18 | ctrl := NewMockController(r) 19 | m := Mock[iface](ctrl) 20 | c := Captor[int]() 21 | WhenSingle(m.Foo(c.Capture())).ThenReturn(10) 22 | m.Foo(11) 23 | r.AssertEqual(c.Last(), 11) 24 | } 25 | 26 | func TestCaptorMatches(t *testing.T) { 27 | r := common.NewMockReporter(t) 28 | ctrl := NewMockController(r) 29 | m := Mock[iface](ctrl) 30 | c := Captor[int]() 31 | WhenSingle(m.Foo(c.Capture())).ThenReturn(10) 32 | ans := m.Foo(11) 33 | r.AssertEqual(ans, 10) 34 | } 35 | 36 | func TestCaptorMultiCalls(t *testing.T) { 37 | r := common.NewMockReporter(t) 38 | ctrl := NewMockController(r) 39 | m := Mock[iface](ctrl) 40 | c := Captor[int]() 41 | WhenSingle(m.Foo(c.Capture())).ThenReturn(10) 42 | m.Foo(11) 43 | m.Foo(12) 44 | r.AssertEqual(c.Last(), 12) 45 | r.AssertEqual(c.Values()[0], 11) 46 | r.AssertEqual(c.Values()[1], 12) 47 | } 48 | 49 | func TestCaptorMultiUsage(t *testing.T) { 50 | r := common.NewMockReporter(t) 51 | ctrl := NewMockController(r) 52 | m1 := Mock[iface](ctrl) 53 | m2 := Mock[iface](ctrl) 54 | c := Captor[int]() 55 | WhenSingle(m1.Foo(c.Capture())).ThenReturn(10) 56 | WhenSingle(m2.Foo(c.Capture())).ThenReturn(10) 57 | m1.Foo(10) 58 | m2.Foo(11) 59 | r.AssertEqual(c.Values()[0], 10) 60 | r.AssertEqual(c.Values()[1], 11) 61 | } 62 | 63 | func TestCaptorVerify(t *testing.T) { 64 | r := common.NewMockReporter(t) 65 | ctrl := NewMockController(r) 66 | m := Mock[iface](ctrl) 67 | c := Captor[int]() 68 | m.VoidFoo(10, 20) 69 | Verify(m, Once()).VoidFoo(c.Capture(), Exact(20)) 70 | r.AssertNoError() 71 | r.AssertEqual(10, c.Last()) 72 | } 73 | -------------------------------------------------------------------------------- /tests/check/check_test.go: -------------------------------------------------------------------------------- 1 | package check 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ovechkin-dm/mockio/v2/tests/common" 7 | 8 | . "github.com/ovechkin-dm/mockio/v2/mock" 9 | ) 10 | 11 | type St struct{} 12 | 13 | func TestNonInterfaceNotAllowed(t *testing.T) { 14 | r := common.NewMockReporter(t) 15 | ctrl := NewMockController(r) 16 | defer func() { 17 | if r := recover(); r == nil { 18 | t.Errorf("expected panic, but code did not panic") 19 | } 20 | }() 21 | _ = Mock[St](ctrl) 22 | r.AssertError() 23 | } 24 | -------------------------------------------------------------------------------- /tests/codegen/codegen_test.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ovechkin-dm/mockio/v2/tests/common" 7 | 8 | . "github.com/ovechkin-dm/mockio/v2/mock" 9 | ) 10 | 11 | func TestGeneratedMock(t *testing.T) { 12 | r := common.NewMockReporter(t) 13 | ctrl := NewMockController(r) 14 | m := NewMockGreeter(ctrl) 15 | WhenSingle(m.Greet(AnyString())).ThenAnswer(func(args []any) string { 16 | return "Hello, " + args[0].(string) 17 | }) 18 | result := m.Greet("John") 19 | r.AssertEqual("Hello, John", result) 20 | Verify(m, Once()).Greet("John") 21 | r.AssertNoError() 22 | } 23 | 24 | func TestGeneratedMockNoMoreInteractionsSuccess(t *testing.T) { 25 | r := common.NewMockReporter(t) 26 | ctrl := NewMockController(r) 27 | m := NewMockGreeter(ctrl) 28 | WhenSingle(m.Greet(AnyString())).ThenAnswer(func(args []any) string { 29 | return "Hello, " + args[0].(string) 30 | }) 31 | result := m.Greet("John") 32 | r.AssertEqual("Hello, John", result) 33 | Verify(m, Once()).Greet("John") 34 | VerifyNoMoreInteractions(m) 35 | r.AssertNoError() 36 | } 37 | 38 | func TestGeneratedMockNoMoreInteractionsFail(t *testing.T) { 39 | r := common.NewMockReporter(t) 40 | ctrl := NewMockController(r) 41 | m := NewMockGreeter(ctrl) 42 | WhenSingle(m.Greet(AnyString())).ThenAnswer(func(args []any) string { 43 | return "Hello, " + args[0].(string) 44 | }) 45 | result := m.Greet("John") 46 | r.AssertEqual("Hello, John", result) 47 | VerifyNoMoreInteractions(m) 48 | r.AssertError() 49 | } 50 | -------------------------------------------------------------------------------- /tests/codegen/ifaces.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | type Greeter interface { 4 | Greet(name string) string 5 | } 6 | -------------------------------------------------------------------------------- /tests/codegen/mocks_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery; DO NOT EDIT. 2 | // github.com/vektra/mockery 3 | // template: mockio 4 | 5 | package codegen 6 | 7 | import ( 8 | "reflect" 9 | 10 | "github.com/ovechkin-dm/mockio/v2/matchers" 11 | ) 12 | 13 | func NewMockGreeter(ctrl *matchers.MockController) Greeter { 14 | tp := reflect.TypeOf(new(Greeter)).Elem() 15 | handler := ctrl.MockFactory.BuildHandler(ctrl.Env, tp) 16 | methodsMap := make(map[string]reflect.Method) 17 | for i := 0; i < tp.NumMethod(); i++ { 18 | method := tp.Method(i) 19 | methodsMap[method.Name] = method 20 | } 21 | mockVar := &MockGreeter{ 22 | _handler: handler, 23 | _methodsMap: methodsMap, 24 | } 25 | 26 | return mockVar 27 | } 28 | 29 | // MockGreeter is an autogenerated mock type for the Greeter type 30 | type MockGreeter struct { 31 | _handler matchers.Handler 32 | _methodsMap map[string]reflect.Method 33 | } 34 | 35 | func (mock *MockGreeter) Handler() matchers.Handler { 36 | return mock._handler 37 | } 38 | 39 | // Greet provides a mock function for the type MockGreeter 40 | func (mock *MockGreeter) Greet(name string) string { 41 | methodType := mock._methodsMap["Greet"] 42 | args := []reflect.Value{ 43 | reflect.ValueOf(name), 44 | } 45 | outs := mock._handler.Handle(methodType, args) 46 | r0, _ := outs[0].Interface().(string) 47 | 48 | return r0 49 | } 50 | -------------------------------------------------------------------------------- /tests/common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | type MockReporter struct { 12 | reported string 13 | t *testing.T 14 | cleanups []func() 15 | fatalCount int 16 | errorCount int 17 | } 18 | 19 | func (m *MockReporter) Fatalf(format string, args ...any) { 20 | m.reported = fmt.Sprintf(format, args...) 21 | m.fatalCount++ 22 | } 23 | 24 | func (m *MockReporter) Errorf(format string, args ...any) { 25 | m.reported = fmt.Sprintf(format, args...) 26 | m.errorCount++ 27 | } 28 | 29 | func (m *MockReporter) IsError() bool { 30 | return m.reported != "" 31 | } 32 | 33 | func (m *MockReporter) ErrorContains(s string) bool { 34 | return m.IsError() && strings.Contains(strings.ToLower(m.reported), strings.ToLower(s)) 35 | } 36 | 37 | func (m *MockReporter) GetErrorString() string { 38 | return m.reported 39 | } 40 | 41 | func (m *MockReporter) GetError() error { 42 | return errors.New(m.reported) 43 | } 44 | 45 | func (m *MockReporter) AssertNoError() { 46 | if m.IsError() { 47 | m.t.Fatalf("Expected no error, got: %s", m.reported) 48 | } 49 | } 50 | 51 | func (m *MockReporter) AssertError() { 52 | if !m.IsError() { 53 | m.t.Fatalf("Expected error, got nothing") 54 | } 55 | } 56 | 57 | func (m *MockReporter) AssertEqual(expected any, actual any) { 58 | if !reflect.DeepEqual(expected, actual) { 59 | m.t.Fatalf("Values not equal. \n Expected: %v \n actual: %v", expected, actual) 60 | } 61 | } 62 | 63 | func (m *MockReporter) AssertErrorContains(err error, s string) { 64 | if err == nil { 65 | m.t.Fatalf("expected error, got nil") 66 | } 67 | if !strings.Contains(err.Error(), s) { 68 | m.t.Fatalf("expected error to contain %s", s) 69 | } 70 | } 71 | 72 | func (m *MockReporter) Cleanup(clean func()) { 73 | m.cleanups = append(m.cleanups, clean) 74 | } 75 | 76 | func (m *MockReporter) TriggerCleanup() { 77 | for _, clean := range m.cleanups { 78 | clean() 79 | } 80 | } 81 | 82 | func (m *MockReporter) PrintError() { 83 | fmt.Println(m.reported) 84 | } 85 | 86 | func (m *MockReporter) GetFatalCount() int { 87 | return m.fatalCount 88 | } 89 | 90 | func (m *MockReporter) GetErrorCount() int { 91 | return m.errorCount 92 | } 93 | 94 | func NewMockReporter(t *testing.T) *MockReporter { 95 | rep := &MockReporter{ 96 | reported: "", 97 | t: t, 98 | cleanups: make([]func(), 0), 99 | } 100 | return rep 101 | } 102 | -------------------------------------------------------------------------------- /tests/concurrent/concurrent_test.go: -------------------------------------------------------------------------------- 1 | package concurrent 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | 7 | "github.com/ovechkin-dm/mockio/v2/tests/common" 8 | 9 | . "github.com/ovechkin-dm/mockio/v2/mock" 10 | ) 11 | 12 | type myInterface interface { 13 | Foo(a int) int 14 | } 15 | 16 | func TestNewMockInOtherFiber(t *testing.T) { 17 | r := common.NewMockReporter(t) 18 | ctrl := NewMockController(r) 19 | m := Mock[myInterface](ctrl) 20 | wg := sync.WaitGroup{} 21 | wg.Add(1) 22 | WhenSingle(m.Foo(Any[int]())).ThenReturn(42) 23 | ans := 0 24 | go func() { 25 | ans = m.Foo(10) 26 | wg.Done() 27 | }() 28 | wg.Wait() 29 | 30 | r.AssertEqual(42, ans) 31 | r.AssertNoError() 32 | } 33 | -------------------------------------------------------------------------------- /tests/match/match_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ovechkin-dm/mockio/v2/tests/common" 7 | 8 | . "github.com/ovechkin-dm/mockio/v2/mock" 9 | ) 10 | 11 | type Iface interface { 12 | Test(i interface{}) bool 13 | } 14 | 15 | type Greeter interface { 16 | Greet(name any) string 17 | } 18 | 19 | type St struct { 20 | value int 21 | } 22 | 23 | type MyStruct struct { 24 | items any 25 | } 26 | 27 | type MyInterface interface { 28 | Test(m *MyStruct) int 29 | } 30 | 31 | type SliceInterface interface { 32 | Test(m []int) int 33 | } 34 | 35 | type MapInterface interface { 36 | Test(m map[int]int) int 37 | } 38 | 39 | func TestAny(t *testing.T) { 40 | r := common.NewMockReporter(t) 41 | ctrl := NewMockController(r) 42 | m := Mock[Iface](ctrl) 43 | WhenSingle(m.Test(Any[string]())).ThenReturn(true) 44 | ret := m.Test("test") 45 | r.AssertEqual(true, ret) 46 | } 47 | 48 | func TestAnyStruct(t *testing.T) { 49 | r := common.NewMockReporter(t) 50 | ctrl := NewMockController(r) 51 | m := Mock[Iface](ctrl) 52 | WhenSingle(m.Test(Any[*St]())).ThenReturn(true) 53 | st := &St{} 54 | ret := m.Test(st) 55 | r.AssertEqual(true, ret) 56 | } 57 | 58 | func TestAnyWrongType(t *testing.T) { 59 | r := common.NewMockReporter(t) 60 | ctrl := NewMockController(r) 61 | m := Mock[Iface](ctrl) 62 | WhenSingle(m.Test(Any[int]())).ThenReturn(true) 63 | ret := m.Test("test") 64 | r.AssertEqual(false, ret) 65 | } 66 | 67 | func TestExactStruct(t *testing.T) { 68 | r := common.NewMockReporter(t) 69 | ctrl := NewMockController(r) 70 | a := St{} 71 | m := Mock[Iface](ctrl) 72 | WhenSingle(m.Test(Exact(&a))).ThenReturn(true) 73 | ret := m.Test(&a) 74 | r.AssertEqual(true, ret) 75 | } 76 | 77 | func TestExactWrongStruct(t *testing.T) { 78 | r := common.NewMockReporter(t) 79 | ctrl := NewMockController(r) 80 | a := &St{10} 81 | b := &St{10} 82 | m := Mock[Iface](ctrl) 83 | WhenSingle(m.Test(Exact(a))).ThenReturn(true) 84 | ret := m.Test(b) 85 | r.AssertEqual(false, ret) 86 | } 87 | 88 | func TestEqualStruct(t *testing.T) { 89 | r := common.NewMockReporter(t) 90 | ctrl := NewMockController(r) 91 | a := &St{10} 92 | b := &St{10} 93 | m := Mock[Iface](ctrl) 94 | WhenSingle(m.Test(Equal(a))).ThenReturn(true) 95 | ret := m.Test(b) 96 | r.AssertEqual(true, ret) 97 | } 98 | 99 | func TestNonEqualStruct(t *testing.T) { 100 | r := common.NewMockReporter(t) 101 | ctrl := NewMockController(r) 102 | a := &St{11} 103 | b := &St{10} 104 | m := Mock[Iface](ctrl) 105 | WhenSingle(m.Test(Equal(a))).ThenReturn(true) 106 | ret := m.Test(b) 107 | r.AssertEqual(false, ret) 108 | } 109 | 110 | func TestCustomMatcher(t *testing.T) { 111 | r := common.NewMockReporter(t) 112 | ctrl := NewMockController(r) 113 | even := CreateMatcher[int]("even", func(allArgs []any, actual int) bool { 114 | return actual%2 == 0 115 | }) 116 | m := Mock[Iface](ctrl) 117 | WhenSingle(m.Test(even())).ThenReturn(true) 118 | ret1 := m.Test(10) 119 | ret2 := m.Test(11) 120 | r.AssertEqual(ret1, true) 121 | r.AssertEqual(ret2, false) 122 | } 123 | 124 | func TestNotEqual(t *testing.T) { 125 | r := common.NewMockReporter(t) 126 | ctrl := NewMockController(r) 127 | m := Mock[Iface](ctrl) 128 | WhenSingle(m.Test(NotEqual("test"))).ThenReturn(true) 129 | ret := m.Test("test1") 130 | r.AssertEqual(true, ret) 131 | } 132 | 133 | func TestOneOf(t *testing.T) { 134 | r := common.NewMockReporter(t) 135 | ctrl := NewMockController(r) 136 | m := Mock[Iface](ctrl) 137 | WhenSingle(m.Test(OneOf("test1", "test2"))).ThenReturn(true) 138 | ret := m.Test("test2") 139 | r.AssertEqual(true, ret) 140 | } 141 | 142 | func TestDeepEqual(t *testing.T) { 143 | r := common.NewMockReporter(t) 144 | ctrl := NewMockController(r) 145 | m := Mock[MyInterface](ctrl) 146 | s1 := MyStruct{ 147 | items: &[]int{1, 2, 3}, 148 | } 149 | s2 := MyStruct{ 150 | items: &[]int{1, 2, 3}, 151 | } 152 | WhenSingle(m.Test(&s1)).ThenReturn(9) 153 | result := m.Test(&s2) 154 | r.AssertEqual(result, 9) 155 | } 156 | 157 | func TestNilMatch(t *testing.T) { 158 | r := common.NewMockReporter(t) 159 | ctrl := NewMockController(r) 160 | m := Mock[Iface](ctrl) 161 | WhenSingle(m.Test(Nil[any]())).ThenReturn(true) 162 | ret := m.Test(nil) 163 | r.AssertEqual(true, ret) 164 | } 165 | 166 | func TestNilNoMatch(t *testing.T) { 167 | r := common.NewMockReporter(t) 168 | ctrl := NewMockController(r) 169 | m := Mock[Iface](ctrl) 170 | WhenSingle(m.Test(Nil[any]())).ThenReturn(true) 171 | ret := m.Test(10) 172 | r.AssertEqual(false, ret) 173 | } 174 | 175 | func TestSubstringMatch(t *testing.T) { 176 | r := common.NewMockReporter(t) 177 | ctrl := NewMockController(r) 178 | m := Mock[Iface](ctrl) 179 | WhenSingle(m.Test(Substring("test"))).ThenReturn(true) 180 | ret := m.Test("123test123") 181 | r.AssertEqual(true, ret) 182 | } 183 | 184 | func TestSubstringNoMatch(t *testing.T) { 185 | r := common.NewMockReporter(t) 186 | ctrl := NewMockController(r) 187 | m := Mock[Iface](ctrl) 188 | WhenSingle(m.Test(Substring("test321"))).ThenReturn(true) 189 | ret := m.Test("123test123") 190 | r.AssertEqual(false, ret) 191 | } 192 | 193 | func TestNotNilMatch(t *testing.T) { 194 | r := common.NewMockReporter(t) 195 | ctrl := NewMockController(r) 196 | m := Mock[Iface](ctrl) 197 | WhenSingle(m.Test(NotNil[any]())).ThenReturn(true) 198 | ret := m.Test(10) 199 | r.AssertEqual(true, ret) 200 | } 201 | 202 | func TestNotNilNoMatch(t *testing.T) { 203 | r := common.NewMockReporter(t) 204 | ctrl := NewMockController(r) 205 | m := Mock[Iface](ctrl) 206 | WhenSingle(m.Test(NotNil[any]())).ThenReturn(true) 207 | ret := m.Test(nil) 208 | r.AssertEqual(false, ret) 209 | } 210 | 211 | func TestRegexMatch(t *testing.T) { 212 | r := common.NewMockReporter(t) 213 | ctrl := NewMockController(r) 214 | m := Mock[Iface](ctrl) 215 | WhenSingle(m.Test(Regex("test"))).ThenReturn(true) 216 | ret := m.Test("123test123") 217 | r.AssertEqual(true, ret) 218 | } 219 | 220 | func TestRegexNoMatch(t *testing.T) { 221 | r := common.NewMockReporter(t) 222 | ctrl := NewMockController(r) 223 | m := Mock[Iface](ctrl) 224 | WhenSingle(m.Test(Regex("test321"))).ThenReturn(true) 225 | ret := m.Test("123test123") 226 | r.AssertEqual(false, ret) 227 | } 228 | 229 | func TestSliceLenMatch(t *testing.T) { 230 | r := common.NewMockReporter(t) 231 | ctrl := NewMockController(r) 232 | m := Mock[SliceInterface](ctrl) 233 | WhenSingle(m.Test(SliceLen[int](3))).ThenReturn(3) 234 | ret := m.Test([]int{1, 2, 3}) 235 | r.AssertEqual(3, ret) 236 | } 237 | 238 | func TestSliceLenNoMatch(t *testing.T) { 239 | r := common.NewMockReporter(t) 240 | ctrl := NewMockController(r) 241 | m := Mock[SliceInterface](ctrl) 242 | WhenSingle(m.Test(SliceLen[int](4))).ThenReturn(3) 243 | ret := m.Test([]int{1, 2, 3}) 244 | r.AssertEqual(0, ret) 245 | } 246 | 247 | func TestMapLenMatch(t *testing.T) { 248 | r := common.NewMockReporter(t) 249 | ctrl := NewMockController(r) 250 | m := Mock[MapInterface](ctrl) 251 | WhenSingle(m.Test(MapLen[int, int](3))).ThenReturn(3) 252 | ret := m.Test(map[int]int{1: 1, 2: 2, 3: 3}) 253 | r.AssertEqual(3, ret) 254 | } 255 | 256 | func TestMapLenNoMatch(t *testing.T) { 257 | r := common.NewMockReporter(t) 258 | ctrl := NewMockController(r) 259 | m := Mock[MapInterface](ctrl) 260 | WhenSingle(m.Test(MapLen[int, int](3))).ThenReturn(3) 261 | ret := m.Test(map[int]int{1: 1, 2: 2, 3: 3, 4: 4}) 262 | r.AssertEqual(0, ret) 263 | } 264 | 265 | func TestSliceContainsMatch(t *testing.T) { 266 | r := common.NewMockReporter(t) 267 | ctrl := NewMockController(r) 268 | m := Mock[SliceInterface](ctrl) 269 | WhenSingle(m.Test(SliceContains[int](3))).ThenReturn(3) 270 | ret := m.Test([]int{1, 2, 3}) 271 | r.AssertEqual(3, ret) 272 | } 273 | 274 | func TestSliceContainsNoMatch(t *testing.T) { 275 | r := common.NewMockReporter(t) 276 | ctrl := NewMockController(r) 277 | m := Mock[SliceInterface](ctrl) 278 | WhenSingle(m.Test(SliceContains[int](4))).ThenReturn(3) 279 | ret := m.Test([]int{1, 2, 3}) 280 | r.AssertEqual(0, ret) 281 | } 282 | 283 | func TestMapContainsMatch(t *testing.T) { 284 | r := common.NewMockReporter(t) 285 | ctrl := NewMockController(r) 286 | m := Mock[MapInterface](ctrl) 287 | WhenSingle(m.Test(MapContains[int, int](3))).ThenReturn(3) 288 | ret := m.Test(map[int]int{1: 1, 2: 2, 3: 3}) 289 | r.AssertEqual(3, ret) 290 | } 291 | 292 | func TestMapContainsNoMatch(t *testing.T) { 293 | r := common.NewMockReporter(t) 294 | ctrl := NewMockController(r) 295 | m := Mock[MapInterface](ctrl) 296 | WhenSingle(m.Test(MapContains[int, int](4))).ThenReturn(3) 297 | ret := m.Test(map[int]int{1: 1, 2: 2, 3: 3}) 298 | r.AssertEqual(0, ret) 299 | } 300 | 301 | func TestSliceEqualUnorderedMatch(t *testing.T) { 302 | r := common.NewMockReporter(t) 303 | ctrl := NewMockController(r) 304 | m := Mock[SliceInterface](ctrl) 305 | WhenSingle(m.Test(SliceEqualUnordered[int]([]int{1, 2, 3}))).ThenReturn(3) 306 | ret := m.Test([]int{3, 2, 1}) 307 | r.AssertEqual(3, ret) 308 | } 309 | 310 | func TestSliceEqualUnorderedNoMatch(t *testing.T) { 311 | r := common.NewMockReporter(t) 312 | ctrl := NewMockController(r) 313 | m := Mock[SliceInterface](ctrl) 314 | WhenSingle(m.Test(SliceEqualUnordered[int]([]int{1, 2, 3}))).ThenReturn(3) 315 | ret := m.Test([]int{3, 2, 1, 4}) 316 | r.AssertEqual(0, ret) 317 | } 318 | 319 | func TestUnexpectedUseOfMatchers(t *testing.T) { 320 | r := common.NewMockReporter(t) 321 | ctrl := NewMockController(r) 322 | m := Mock[Iface](ctrl) 323 | m.Test(AnyString()) 324 | Verify(m, Once()).Test("test") 325 | r.AssertErrorContains(r.GetError(), "Unexpected matchers declaration") 326 | } 327 | 328 | func TestExactNotComparable(t *testing.T) { 329 | ctrl := NewMockController(t) 330 | greeter := Mock[Greeter](ctrl) 331 | var data any = []int{1, 2} 332 | When(greeter.Greet(Exact(data))).ThenReturn("hello world") 333 | greeter.Greet(data) 334 | } 335 | -------------------------------------------------------------------------------- /tests/mocking/mock_test.go: -------------------------------------------------------------------------------- 1 | package mocking 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/ovechkin-dm/mockio/v2/tests/common" 8 | 9 | . "github.com/ovechkin-dm/mockio/v2/mock" 10 | ) 11 | 12 | type ByteArrInterface interface { 13 | DoSomething(b [16]byte) string 14 | } 15 | type OtherIface interface { 16 | SomeMethod() bool 17 | } 18 | 19 | type CallingIface interface { 20 | GetMocked(appClient OtherIface) OtherIface 21 | } 22 | 23 | type SingleArgIface interface { 24 | SingleArgMethod(other OtherIface) error 25 | } 26 | 27 | type MultiMethod interface { 28 | One(int) int 29 | Two(int) int 30 | Three(int) int 31 | Four(int) int 32 | } 33 | 34 | type ParentIface interface { 35 | Foo(int) int 36 | } 37 | 38 | type ChildIface interface { 39 | ParentIface 40 | Bar(int) int 41 | } 42 | 43 | type PrivateIface interface { 44 | privateMethod() bool 45 | } 46 | 47 | func TestMockWithMockedArg(t *testing.T) { 48 | r := common.NewMockReporter(t) 49 | ctrl := NewMockController(r) 50 | callingMock := Mock[CallingIface](ctrl) 51 | otherMock := Mock[OtherIface](ctrl) 52 | WhenSingle(callingMock.GetMocked(Exact(otherMock))).ThenReturn(otherMock) 53 | res := callingMock.GetMocked(otherMock) 54 | Verify(callingMock, Times(1)).GetMocked(Exact(otherMock)) 55 | VerifyNoMoreInteractions(callingMock) 56 | r.AssertEqual(otherMock, res) 57 | r.AssertNoError() 58 | } 59 | 60 | func TestByteArrayArgs(t *testing.T) { 61 | r := common.NewMockReporter(t) 62 | ctrl := NewMockController(r) 63 | myMock := Mock[ByteArrInterface](ctrl) 64 | myBytes := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} 65 | WhenSingle(myMock.DoSomething(myBytes)).ThenReturn("test") 66 | result := myMock.DoSomething(myBytes) 67 | r.AssertEqual(result, "test") 68 | } 69 | 70 | func TestNilArgs(t *testing.T) { 71 | r := common.NewMockReporter(t) 72 | ctrl := NewMockController(r) 73 | myMock := Mock[SingleArgIface](ctrl) 74 | WhenSingle(myMock.SingleArgMethod(Any[OtherIface]())).ThenReturn(errors.New("test")) 75 | result := myMock.SingleArgMethod(nil) 76 | r.AssertEqual(result.Error(), "test") 77 | } 78 | 79 | func TestMultiMethodOrder(t *testing.T) { 80 | r := common.NewMockReporter(t) 81 | ctrl := NewMockController(r) 82 | myMock := Mock[MultiMethod](ctrl) 83 | WhenSingle(myMock.One(1)).ThenReturn(1) 84 | WhenSingle(myMock.Two(2)).ThenReturn(2) 85 | WhenSingle(myMock.Three(3)).ThenReturn(3) 86 | WhenSingle(myMock.Four(4)).ThenReturn(4) 87 | r.AssertEqual(myMock.One(1), 1) 88 | r.AssertEqual(myMock.Two(2), 2) 89 | r.AssertEqual(myMock.Three(3), 3) 90 | r.AssertEqual(myMock.Four(4), 4) 91 | } 92 | 93 | func TestMockSimpleCasting(t *testing.T) { 94 | r := common.NewMockReporter(t) 95 | ctrl := NewMockController(r) 96 | myMock := Mock[OtherIface](ctrl) 97 | WhenSingle(myMock.SomeMethod()).ThenReturn(true) 98 | var casted any = myMock 99 | source := casted.(OtherIface) 100 | result := source.SomeMethod() 101 | r.AssertEqual(result, true) 102 | } 103 | 104 | func TestMockCasting(t *testing.T) { 105 | r := common.NewMockReporter(t) 106 | ctrl := NewMockController(r) 107 | myMock := Mock[ChildIface](ctrl) 108 | WhenSingle(myMock.Foo(1)).ThenReturn(1) 109 | WhenSingle(myMock.Bar(1)).ThenReturn(2) 110 | var casted any = myMock 111 | source := casted.(ParentIface) 112 | result := source.Foo(1) 113 | r.AssertEqual(result, 1) 114 | } 115 | 116 | func TestMockPrivate(t *testing.T) { 117 | r := common.NewMockReporter(t) 118 | ctrl := NewMockController(r) 119 | myMock := Mock[PrivateIface](ctrl) 120 | WhenSingle(myMock.privateMethod()).ThenReturn(true) 121 | var casted any = myMock 122 | source := casted.(PrivateIface) 123 | result := source.privateMethod() 124 | r.AssertNoError() 125 | r.AssertEqual(result, true) 126 | } 127 | -------------------------------------------------------------------------------- /tests/reporting/error_reporting_test.go: -------------------------------------------------------------------------------- 1 | package reporting 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | 7 | "github.com/ovechkin-dm/mockio/v2/mockopts" 8 | "github.com/ovechkin-dm/mockio/v2/tests/common" 9 | 10 | . "github.com/ovechkin-dm/mockio/v2/mock" 11 | ) 12 | 13 | type Foo interface { 14 | Bar() 15 | Baz(a int, b int, c int) int 16 | VarArgs(a string, b ...int) int 17 | } 18 | 19 | func TestReportIncorrectWhenUsage(t *testing.T) { 20 | defer func() { 21 | if err := recover(); err == nil { 22 | t.Errorf("Expected panic but got none") 23 | } 24 | }() 25 | When(1) 26 | } 27 | 28 | func TestVerifyFromDifferentGoroutine(t *testing.T) { 29 | r := common.NewMockReporter(t) 30 | ctrl := NewMockController(r) 31 | mock := Mock[Foo](ctrl) 32 | wg := sync.WaitGroup{} 33 | wg.Add(1) 34 | go func() { 35 | Verify(mock, Once()) 36 | wg.Done() 37 | }() 38 | wg.Wait() 39 | r.AssertNoError() 40 | } 41 | 42 | func TestReportVerifyNotAMock(t *testing.T) { 43 | defer func() { 44 | if err := recover(); err == nil { 45 | t.Errorf("Expected panic but got none") 46 | } 47 | }() 48 | Verify(100, Once()) 49 | } 50 | 51 | func TestInvalidUseOfMatchers(t *testing.T) { 52 | r := common.NewMockReporter(t) 53 | ctrl := NewMockController(r) 54 | mock := Mock[Foo](ctrl) 55 | When(mock.Baz(AnyInt(), AnyInt(), 10)).ThenReturn(10) 56 | mock.Baz(1, 2, 3) 57 | r.AssertError() 58 | r.PrintError() 59 | } 60 | 61 | func TestInvalidUseOfMatchersVarArgs(t *testing.T) { 62 | r := common.NewMockReporter(t) 63 | ctrl := NewMockController(r) 64 | mock := Mock[Foo](ctrl) 65 | When(mock.VarArgs(AnyString(), AnyInt(), 10)).ThenReturn(10) 66 | mock.VarArgs("a", 2) 67 | r.AssertError() 68 | r.PrintError() 69 | } 70 | 71 | func TestCaptorInsideVerify(t *testing.T) { 72 | r := common.NewMockReporter(t) 73 | ctrl := NewMockController(r) 74 | mock := Mock[Foo](ctrl) 75 | When(mock.Baz(AnyInt(), AnyInt(), AnyInt())).ThenReturn(10) 76 | c := Captor[int]() 77 | Verify(mock, Once()).Baz(AnyInt(), AnyInt(), c.Capture()) 78 | r.AssertError() 79 | r.PrintError() 80 | } 81 | 82 | func TestVerify(t *testing.T) { 83 | r := common.NewMockReporter(t) 84 | ctrl := NewMockController(r) 85 | mock := Mock[Foo](ctrl) 86 | When(mock.Baz(AnyInt(), AnyInt(), AnyInt())).ThenReturn(10) 87 | _ = mock.Baz(10, 10, 11) 88 | Verify(mock, Once()).Baz(AnyInt(), AnyInt(), Exact(10)) 89 | r.AssertError() 90 | r.PrintError() 91 | } 92 | 93 | func TestVerifyVarArgs(t *testing.T) { 94 | r := common.NewMockReporter(t) 95 | ctrl := NewMockController(r) 96 | mock := Mock[Foo](ctrl) 97 | When(mock.VarArgs(AnyString(), AnyInt(), AnyInt())).ThenReturn(10) 98 | _ = mock.VarArgs("a", 10, 11) 99 | Verify(mock, Once()).VarArgs(AnyString(), AnyInt(), Exact(10)) 100 | r.AssertError() 101 | r.PrintError() 102 | } 103 | 104 | func TestVerifyDifferentVarArgs(t *testing.T) { 105 | r := common.NewMockReporter(t) 106 | ctrl := NewMockController(r) 107 | mock := Mock[Foo](ctrl) 108 | When(mock.VarArgs(AnyString(), AnyInt(), AnyInt())).ThenReturn(10) 109 | _ = mock.VarArgs("a", 10, 11) 110 | Verify(mock, Once()).VarArgs(AnyString(), AnyInt(), AnyInt(), AnyInt()) 111 | r.AssertError() 112 | r.PrintError() 113 | } 114 | 115 | func TestVerifyTimes(t *testing.T) { 116 | r := common.NewMockReporter(t) 117 | ctrl := NewMockController(r) 118 | mock := Mock[Foo](ctrl) 119 | When(mock.Baz(AnyInt(), AnyInt(), AnyInt())).ThenReturn(10) 120 | _ = mock.Baz(10, 10, 10) 121 | Verify(mock, Times(20)).Baz(AnyInt(), AnyInt(), AnyInt()) 122 | r.AssertError() 123 | r.PrintError() 124 | } 125 | 126 | func TestEmptyCaptor(t *testing.T) { 127 | defer func() { 128 | if err := recover(); err == nil { 129 | t.Errorf("Expected panic but got none") 130 | } 131 | }() 132 | c := Captor[int]() 133 | _ = c.Last() 134 | } 135 | 136 | func TestInvalidReturnValues(t *testing.T) { 137 | r := common.NewMockReporter(t) 138 | ctrl := NewMockController(r) 139 | mock := Mock[Foo](ctrl) 140 | When(mock.Baz(AnyInt(), AnyInt(), AnyInt())).ThenReturn("test", 10) 141 | _ = mock.Baz(10, 10, 10) 142 | r.AssertError() 143 | r.PrintError() 144 | } 145 | 146 | func TestNoMoreInteractions(t *testing.T) { 147 | r := common.NewMockReporter(t) 148 | ctrl := NewMockController(r) 149 | mock := Mock[Foo](ctrl) 150 | When(mock.Baz(AnyInt(), AnyInt(), AnyInt())).ThenReturn("test", 10) 151 | _ = mock.Baz(10, 10, 10) 152 | _ = mock.Baz(10, 20, 10) 153 | VerifyNoMoreInteractions(mock) 154 | r.AssertError() 155 | r.PrintError() 156 | } 157 | 158 | func TestNoMoreInteractionsVarArgs(t *testing.T) { 159 | r := common.NewMockReporter(t) 160 | ctrl := NewMockController(r) 161 | mock := Mock[Foo](ctrl) 162 | When(mock.VarArgs(AnyString(), AnyInt(), AnyInt())).ThenReturn("test", 10) 163 | _ = mock.Baz(10, 10, 10) 164 | _ = mock.Baz(10, 20, 10) 165 | VerifyNoMoreInteractions(mock) 166 | r.AssertError() 167 | r.PrintError() 168 | } 169 | 170 | func TestUnexpectedMatchers(t *testing.T) { 171 | r := common.NewMockReporter(t) 172 | ctrl := NewMockController(r) 173 | mock := Mock[Foo](ctrl) 174 | When(mock.Baz(AnyInt(), AnyInt(), AnyInt())).ThenReturn(10) 175 | mock.Baz(AnyInt(), AnyInt(), AnyInt()) 176 | Verify(mock, Once()).Baz(10, 10, 10) 177 | r.AssertError() 178 | r.PrintError() 179 | } 180 | 181 | func TestStackTraceDisabled(t *testing.T) { 182 | r := common.NewMockReporter(t) 183 | ctrl := NewMockController(r, mockopts.WithoutStackTrace()) 184 | mock := Mock[Foo](ctrl) 185 | WhenSingle(mock.Baz(1, 2, AnyInt())).ThenReturn(10) 186 | _ = mock.Baz(1, 2, 3) 187 | r.AssertError() 188 | r.PrintError() 189 | } 190 | 191 | func TestStackTraceEnabled(t *testing.T) { 192 | r := common.NewMockReporter(t) 193 | ctrl := NewMockController(r) 194 | mock := Mock[Foo](ctrl) 195 | WhenSingle(mock.Baz(1, 2, AnyInt())).ThenReturn(10) 196 | _ = mock.Baz(1, 2, 3) 197 | r.AssertError() 198 | r.PrintError() 199 | } 200 | 201 | func TestImplicitMatchersLogValue(t *testing.T) { 202 | r := common.NewMockReporter(t) 203 | ctrl := NewMockController(r) 204 | mock := Mock[Foo](ctrl) 205 | WhenSingle(mock.Baz(1, 2, 3)).ThenReturn(10).Verify(Once()) 206 | r.TriggerCleanup() 207 | r.AssertError() 208 | r.PrintError() 209 | } 210 | -------------------------------------------------------------------------------- /tests/returners/returners_test.go: -------------------------------------------------------------------------------- 1 | package returners 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ovechkin-dm/mockio/v2/tests/common" 7 | 8 | . "github.com/ovechkin-dm/mockio/v2/mock" 9 | ) 10 | 11 | type iface interface { 12 | Test(i interface{}) bool 13 | Foo(i int) int 14 | } 15 | 16 | func TestReturnSimple(t *testing.T) { 17 | r := common.NewMockReporter(t) 18 | ctrl := NewMockController(r) 19 | m := Mock[iface](ctrl) 20 | WhenSingle(m.Test(10)).ThenReturn(true) 21 | ret := m.Test(10) 22 | r.AssertEqual(true, ret) 23 | } 24 | 25 | func TestAnswerSimple(t *testing.T) { 26 | r := common.NewMockReporter(t) 27 | ctrl := NewMockController(r) 28 | m := Mock[iface](ctrl) 29 | WhenSingle(m.Test(10)).ThenAnswer(func(args []any) bool { 30 | return args[0].(int) > 0 31 | }) 32 | ret1 := m.Test(10) 33 | ret2 := m.Test(-10) 34 | r.AssertEqual(true, ret1) 35 | r.AssertEqual(false, ret2) 36 | } 37 | 38 | func TestMultiReturn(t *testing.T) { 39 | r := common.NewMockReporter(t) 40 | ctrl := NewMockController(r) 41 | m := Mock[iface](ctrl) 42 | WhenSingle(m.Foo(10)). 43 | ThenReturn(1). 44 | ThenReturn(2) 45 | ret1 := m.Foo(10) 46 | ret2 := m.Foo(10) 47 | ret3 := m.Foo(10) 48 | r.AssertEqual(1, ret1) 49 | r.AssertEqual(2, ret2) 50 | r.AssertEqual(2, ret3) 51 | } 52 | 53 | func TestMultiAnswer(t *testing.T) { 54 | r := common.NewMockReporter(t) 55 | ctrl := NewMockController(r) 56 | m := Mock[iface](ctrl) 57 | WhenSingle(m.Foo(10)). 58 | ThenAnswer(func(args []any) int { 59 | return 1 60 | }). 61 | ThenAnswer(func(args []any) int { 62 | return 2 63 | }) 64 | ret1 := m.Foo(10) 65 | ret2 := m.Foo(10) 66 | ret3 := m.Foo(10) 67 | r.AssertEqual(1, ret1) 68 | r.AssertEqual(2, ret2) 69 | r.AssertEqual(2, ret3) 70 | } 71 | 72 | func TestReturnBetweenCalls(t *testing.T) { 73 | r := common.NewMockReporter(t) 74 | ctrl := NewMockController(r) 75 | m := Mock[iface](ctrl) 76 | ret := WhenSingle(m.Foo(10)) 77 | ret.ThenReturn(1) 78 | r1 := m.Foo(10) 79 | ret.ThenReturn(2) 80 | r2 := m.Foo(10) 81 | r3 := m.Foo(10) 82 | r.AssertEqual(1, r1) 83 | r.AssertEqual(2, r2) 84 | r.AssertEqual(2, r3) 85 | } 86 | 87 | func TestReturnWrongType(t *testing.T) { 88 | r := common.NewMockReporter(t) 89 | ctrl := NewMockController(r) 90 | m := Mock[iface](ctrl) 91 | When(m.Test(Any[any]())).ThenReturn(10) 92 | m.Test(10) 93 | r.AssertError() 94 | } 95 | -------------------------------------------------------------------------------- /tests/simple/simple_test.go: -------------------------------------------------------------------------------- 1 | package simple 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ovechkin-dm/mockio/v2/tests/common" 7 | 8 | . "github.com/ovechkin-dm/mockio/v2/mock" 9 | ) 10 | 11 | type myInterface interface { 12 | Foo(a int) int 13 | } 14 | 15 | type myGenericInterface[T any] interface { 16 | Foo(a ...T) T 17 | } 18 | 19 | func TestSimple(t *testing.T) { 20 | r := common.NewMockReporter(t) 21 | ctrl := NewMockController(r) 22 | m := Mock[myInterface](ctrl) 23 | WhenSingle(m.Foo(Any[int]())).ThenReturn(42) 24 | ret := m.Foo(10) 25 | r.AssertEqual(42, ret) 26 | Verify(m, AtLeastOnce()).Foo(10) 27 | } 28 | 29 | func TestGenericSimple(t *testing.T) { 30 | r := common.NewMockReporter(t) 31 | ctrl := NewMockController(r) 32 | m := Mock[myGenericInterface[int]](ctrl) 33 | WhenSingle(m.Foo(Any[int](), AnyInt())).ThenReturn(42) 34 | ret := m.Foo(10, 20) 35 | r.AssertEqual(42, ret) 36 | Verify(m, AtLeastOnce()).Foo(10) 37 | } 38 | -------------------------------------------------------------------------------- /tests/variadic/variadic_test.go: -------------------------------------------------------------------------------- 1 | package variadic 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ovechkin-dm/mockio/v2/tests/common" 7 | 8 | . "github.com/ovechkin-dm/mockio/v2/mock" 9 | ) 10 | 11 | type myInterface interface { 12 | Foo(a ...int) int 13 | } 14 | 15 | func TestVariadicSimple(t *testing.T) { 16 | r := common.NewMockReporter(t) 17 | ctrl := NewMockController(r) 18 | m := Mock[myInterface](ctrl) 19 | WhenSingle(m.Foo(1, 1)).ThenReturn(1) 20 | WhenSingle(m.Foo(1)).ThenReturn(2) 21 | ret := m.Foo(1) 22 | r.AssertEqual(2, ret) 23 | Verify(m, AtLeastOnce()).Foo(1) 24 | r.AssertNoError() 25 | } 26 | 27 | func TestCaptor(t *testing.T) { 28 | r := common.NewMockReporter(t) 29 | ctrl := NewMockController(r) 30 | m := Mock[myInterface](ctrl) 31 | c1 := Captor[int]() 32 | c2 := Captor[int]() 33 | WhenSingle(m.Foo(c1.Capture(), c2.Capture())).ThenReturn(1) 34 | ret := m.Foo(1, 2) 35 | r.AssertEqual(1, ret) 36 | r.AssertEqual(c1.Last(), 1) 37 | r.AssertEqual(c2.Last(), 2) 38 | r.AssertNoError() 39 | } 40 | -------------------------------------------------------------------------------- /tests/verify/verify_test.go: -------------------------------------------------------------------------------- 1 | package verify 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ovechkin-dm/mockio/v2/mockopts" 7 | "github.com/ovechkin-dm/mockio/v2/tests/common" 8 | 9 | . "github.com/ovechkin-dm/mockio/v2/mock" 10 | ) 11 | 12 | type iface interface { 13 | Foo(a int) int 14 | } 15 | 16 | type ifaceMockArg interface { 17 | MockAsArg(m iface) 18 | } 19 | 20 | func TestVerifySimple(t *testing.T) { 21 | r := common.NewMockReporter(t) 22 | ctrl := NewMockController(r) 23 | m := Mock[iface](ctrl) 24 | WhenSingle(m.Foo(Any[int]())).ThenReturn(10) 25 | m.Foo(10) 26 | Verify(m, Once()).Foo(10) 27 | r.AssertNoError() 28 | } 29 | 30 | func TestVerifyAny(t *testing.T) { 31 | r := common.NewMockReporter(t) 32 | ctrl := NewMockController(r) 33 | m := Mock[iface](ctrl) 34 | WhenSingle(m.Foo(Any[int]())).ThenReturn(10) 35 | m.Foo(10) 36 | Verify(m, Once()).Foo(Any[int]()) 37 | r.AssertNoError() 38 | } 39 | 40 | func TestVerifyMultipleAny(t *testing.T) { 41 | r := common.NewMockReporter(t) 42 | ctrl := NewMockController(r) 43 | m := Mock[iface](ctrl) 44 | WhenSingle(m.Foo(Any[int]())).ThenReturn(10) 45 | m.Foo(10) 46 | m.Foo(11) 47 | Verify(m, Times(2)).Foo(Any[int]()) 48 | r.AssertNoError() 49 | } 50 | 51 | func TestVerifyNever(t *testing.T) { 52 | r := common.NewMockReporter(t) 53 | ctrl := NewMockController(r) 54 | m := Mock[iface](ctrl) 55 | WhenSingle(m.Foo(Any[int]())).ThenReturn(10) 56 | m.Foo(10) 57 | m.Foo(11) 58 | Verify(m, Never()).Foo(13) 59 | r.AssertNoError() 60 | } 61 | 62 | func TestVerifyNeverFails(t *testing.T) { 63 | r := common.NewMockReporter(t) 64 | ctrl := NewMockController(r) 65 | m := Mock[iface](ctrl) 66 | WhenSingle(m.Foo(Any[int]())).ThenReturn(10) 67 | m.Foo(10) 68 | m.Foo(11) 69 | Verify(m, Never()).Foo(10) 70 | r.AssertError() 71 | } 72 | 73 | func TestNoMoreInteractionsFails(t *testing.T) { 74 | r := common.NewMockReporter(t) 75 | ctrl := NewMockController(r) 76 | m := Mock[iface](ctrl) 77 | WhenSingle(m.Foo(Any[int]())).ThenReturn(10) 78 | m.Foo(10) 79 | VerifyNoMoreInteractions(m) 80 | r.AssertError() 81 | } 82 | 83 | func TestNoMoreInteractionsSuccess(t *testing.T) { 84 | r := common.NewMockReporter(t) 85 | ctrl := NewMockController(r) 86 | m := Mock[iface](ctrl) 87 | WhenSingle(m.Foo(Any[int]())).ThenReturn(10) 88 | m.Foo(10) 89 | Verify(m, Once()).Foo(10) 90 | VerifyNoMoreInteractions(m) 91 | r.AssertNoError() 92 | } 93 | 94 | func TestNoMoreInteractionsComplexFail(t *testing.T) { 95 | r := common.NewMockReporter(t) 96 | ctrl := NewMockController(r) 97 | m := Mock[iface](ctrl) 98 | WhenSingle(m.Foo(10)).ThenReturn(10) 99 | WhenSingle(m.Foo(11)).ThenReturn(10) 100 | m.Foo(10) 101 | m.Foo(11) 102 | Verify(m, Once()).Foo(10) 103 | VerifyNoMoreInteractions(m) 104 | r.AssertError() 105 | } 106 | 107 | func TestNoMoreInteractionsComplexSuccess(t *testing.T) { 108 | r := common.NewMockReporter(t) 109 | ctrl := NewMockController(r) 110 | m := Mock[iface](ctrl) 111 | WhenSingle(m.Foo(10)).ThenReturn(10) 112 | WhenSingle(m.Foo(11)).ThenReturn(10) 113 | m.Foo(10) 114 | m.Foo(11) 115 | Verify(m, AtLeastOnce()).Foo(AnyInt()) 116 | Verify(m, Once()).Foo(11) 117 | VerifyNoMoreInteractions(m) 118 | r.AssertNoError() 119 | } 120 | 121 | func TestVerifyInsideReturnerPass(t *testing.T) { 122 | r := common.NewMockReporter(t) 123 | ctrl := NewMockController(r) 124 | m := Mock[iface](ctrl) 125 | WhenSingle(m.Foo(AnyInt())).ThenReturn(11).Verify(Once()) 126 | m.Foo(10) 127 | r.TriggerCleanup() 128 | r.AssertNoError() 129 | } 130 | 131 | func TestVerifyInsideReturnerNoMoreInteractionsFail(t *testing.T) { 132 | r := common.NewMockReporter(t) 133 | ctrl := NewMockController(r) 134 | m := Mock[iface](ctrl) 135 | WhenSingle(m.Foo(AnyInt())).ThenReturn(11).Verify(Once()) 136 | VerifyNoMoreInteractions(m) 137 | r.AssertError() 138 | } 139 | 140 | func TestVerifyInsideReturnerFail(t *testing.T) { 141 | r := common.NewMockReporter(t) 142 | ctrl := NewMockController(r) 143 | m := Mock[iface](ctrl) 144 | WhenSingle(m.Foo(AnyInt())).ThenReturn(11).Verify(Once()) 145 | r.TriggerCleanup() 146 | r.AssertError() 147 | } 148 | 149 | func TestVerifyMockAsArg(t *testing.T) { 150 | r := common.NewMockReporter(t) 151 | ctrl := NewMockController(r) 152 | m := Mock[iface](ctrl) 153 | m2 := Mock[ifaceMockArg](ctrl) 154 | 155 | m2.MockAsArg(m) 156 | 157 | Verify(m2, Once()).MockAsArg(Any[iface]()) 158 | 159 | r.AssertNoError() 160 | } 161 | 162 | func TestPostponedVerifyNotFailingImmediately(t *testing.T) { 163 | r := common.NewMockReporter(t) 164 | ctrl := NewMockController(r) 165 | m := Mock[iface](ctrl) 166 | WhenSingle(m.Foo(12)).ThenReturn(11).Verify(Once()) 167 | m.Foo(10) 168 | r.TriggerCleanup() 169 | r.AssertError() 170 | r.AssertEqual(r.GetErrorCount(), 1) 171 | } 172 | 173 | func TestStrictVerifyUnwantedInvocation(t *testing.T) { 174 | r := common.NewMockReporter(t) 175 | ctrl := NewMockController(r, mockopts.StrictVerify()) 176 | m := Mock[iface](ctrl) 177 | m.Foo(12) 178 | r.TriggerCleanup() 179 | r.AssertError() 180 | } 181 | 182 | func TestStrictVerifyUnverifiedStub(t *testing.T) { 183 | r := common.NewMockReporter(t) 184 | ctrl := NewMockController(r, mockopts.StrictVerify()) 185 | m := Mock[iface](ctrl) 186 | WhenSingle(m.Foo(12)).ThenReturn(11) 187 | r.TriggerCleanup() 188 | r.AssertError() 189 | } 190 | 191 | func TestVerifyNeverInReturner(t *testing.T) { 192 | ctrl := NewMockController(t, mockopts.StrictVerify()) 193 | m := Mock[iface](ctrl) 194 | WhenSingle(m.Foo(12)).Verify(Never()) 195 | } 196 | -------------------------------------------------------------------------------- /tests/when/when_double_test.go: -------------------------------------------------------------------------------- 1 | package when 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/ovechkin-dm/mockio/v2/tests/common" 8 | 9 | . "github.com/ovechkin-dm/mockio/v2/mock" 10 | ) 11 | 12 | type WhenDoubleInterface interface { 13 | Foo(a int) (int, error) 14 | FooNullable(a int) (*int, error) 15 | } 16 | 17 | func TestWhenDoubleRet(t *testing.T) { 18 | r := common.NewMockReporter(t) 19 | ctrl := NewMockController(r) 20 | m := Mock[WhenDoubleInterface](ctrl) 21 | WhenDouble(m.Foo(Any[int]())).ThenReturn(42, nil) 22 | ret, _ := m.Foo(10) 23 | r.AssertEqual(42, ret) 24 | } 25 | 26 | func TestWhenDoubleAnswer(t *testing.T) { 27 | r := common.NewMockReporter(t) 28 | ctrl := NewMockController(r) 29 | m := Mock[WhenDoubleInterface](ctrl) 30 | WhenDouble(m.Foo(Any[int]())).ThenAnswer(func(args []any) (int, error) { 31 | return 42, nil 32 | }) 33 | ret, _ := m.Foo(10) 34 | r.AssertEqual(42, ret) 35 | } 36 | 37 | func TestWhenDoubleAnswerWithArgs(t *testing.T) { 38 | r := common.NewMockReporter(t) 39 | ctrl := NewMockController(r) 40 | m := Mock[WhenDoubleInterface](ctrl) 41 | WhenDouble(m.Foo(Any[int]())).ThenAnswer(func(args []any) (int, error) { 42 | return args[0].(int) + 1, nil 43 | }) 44 | ret, _ := m.Foo(10) 45 | r.AssertEqual(11, ret) 46 | } 47 | 48 | func TestWhenDoubleMultiAnswer(t *testing.T) { 49 | r := common.NewMockReporter(t) 50 | ctrl := NewMockController(r) 51 | m := Mock[WhenDoubleInterface](ctrl) 52 | WhenDouble(m.Foo(Any[int]())). 53 | ThenAnswer(func(args []any) (int, error) { 54 | return args[0].(int) + 1, nil 55 | }). 56 | ThenAnswer(func(args []any) (int, error) { 57 | return args[0].(int) + 2, nil 58 | }) 59 | ret1, _ := m.Foo(10) 60 | ret2, _ := m.Foo(11) 61 | r.AssertEqual(11, ret1) 62 | r.AssertEqual(13, ret2) 63 | } 64 | 65 | func TestWhenDoubleMultiReturn(t *testing.T) { 66 | r := common.NewMockReporter(t) 67 | ctrl := NewMockController(r) 68 | m := Mock[WhenDoubleInterface](ctrl) 69 | WhenDouble(m.Foo(Any[int]())). 70 | ThenReturn(10, nil). 71 | ThenReturn(11, nil) 72 | ret1, _ := m.Foo(12) 73 | ret2, _ := m.Foo(13) 74 | r.AssertEqual(10, ret1) 75 | r.AssertEqual(11, ret2) 76 | } 77 | 78 | func TestWhenDoubleAnswerAndReturn(t *testing.T) { 79 | r := common.NewMockReporter(t) 80 | ctrl := NewMockController(r) 81 | m := Mock[WhenDoubleInterface](ctrl) 82 | WhenDouble(m.Foo(Any[int]())). 83 | ThenReturn(10, nil). 84 | ThenAnswer(func(args []any) (int, error) { 85 | return args[0].(int) + 1, nil 86 | }). 87 | ThenReturn(11, nil) 88 | ret1, _ := m.Foo(12) 89 | ret2, _ := m.Foo(14) 90 | ret3, _ := m.Foo(15) 91 | r.AssertEqual(10, ret1) 92 | r.AssertEqual(15, ret2) 93 | r.AssertEqual(11, ret3) 94 | } 95 | 96 | func TestWhenDoubleReturnError(t *testing.T) { 97 | r := common.NewMockReporter(t) 98 | ctrl := NewMockController(r) 99 | m := Mock[WhenDoubleInterface](ctrl) 100 | WhenDouble(m.Foo(Any[int]())). 101 | ThenReturn(1, errors.New("err")) 102 | ret, err := m.Foo(12) 103 | r.AssertEqual(1, ret) 104 | r.AssertErrorContains(err, "err") 105 | } 106 | 107 | func TestWhenDoubleAnswerError(t *testing.T) { 108 | r := common.NewMockReporter(t) 109 | ctrl := NewMockController(r) 110 | m := Mock[WhenDoubleInterface](ctrl) 111 | WhenDouble(m.Foo(Any[int]())). 112 | ThenAnswer(func(args []any) (int, error) { 113 | return 0, errors.New("err") 114 | }) 115 | ret, err := m.Foo(12) 116 | r.AssertEqual(0, ret) 117 | r.AssertErrorContains(err, "err") 118 | } 119 | -------------------------------------------------------------------------------- /tests/when/when_single_test.go: -------------------------------------------------------------------------------- 1 | package when 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ovechkin-dm/mockio/v2/tests/common" 7 | 8 | . "github.com/ovechkin-dm/mockio/v2/mock" 9 | ) 10 | 11 | type whenSingleInterface interface { 12 | Foo(a int) int 13 | } 14 | 15 | type whenSingleMockAsArgInterface interface { 16 | MockAsArg(m whenSingleInterface) bool 17 | } 18 | 19 | func TestWhenSingleRet(t *testing.T) { 20 | r := common.NewMockReporter(t) 21 | ctrl := NewMockController(r) 22 | m := Mock[whenSingleInterface](ctrl) 23 | WhenSingle(m.Foo(Any[int]())).ThenReturn(42) 24 | ret := m.Foo(10) 25 | r.AssertEqual(42, ret) 26 | } 27 | 28 | func TestWhenSingleAnswer(t *testing.T) { 29 | r := common.NewMockReporter(t) 30 | ctrl := NewMockController(r) 31 | m := Mock[whenSingleInterface](ctrl) 32 | WhenSingle(m.Foo(Any[int]())).ThenAnswer(func(args []any) int { 33 | return 42 34 | }) 35 | ret := m.Foo(10) 36 | r.AssertEqual(42, ret) 37 | } 38 | 39 | func TestWhenSingleAnswerWithArgs(t *testing.T) { 40 | r := common.NewMockReporter(t) 41 | ctrl := NewMockController(r) 42 | m := Mock[whenSingleInterface](ctrl) 43 | WhenSingle(m.Foo(Any[int]())).ThenAnswer(func(args []any) int { 44 | return args[0].(int) + 1 45 | }) 46 | ret := m.Foo(10) 47 | r.AssertEqual(11, ret) 48 | } 49 | 50 | func TestWhenSingleMultiAnswer(t *testing.T) { 51 | r := common.NewMockReporter(t) 52 | ctrl := NewMockController(r) 53 | m := Mock[whenSingleInterface](ctrl) 54 | WhenSingle(m.Foo(Any[int]())). 55 | ThenAnswer(func(args []any) int { 56 | return args[0].(int) + 1 57 | }). 58 | ThenAnswer(func(args []any) int { 59 | return args[0].(int) + 2 60 | }) 61 | ret1 := m.Foo(10) 62 | ret2 := m.Foo(11) 63 | r.AssertEqual(11, ret1) 64 | r.AssertEqual(13, ret2) 65 | } 66 | 67 | func TestWhenSingleMultiReturn(t *testing.T) { 68 | r := common.NewMockReporter(t) 69 | ctrl := NewMockController(r) 70 | m := Mock[whenSingleInterface](ctrl) 71 | WhenSingle(m.Foo(Any[int]())). 72 | ThenReturn(10). 73 | ThenReturn(11) 74 | ret1 := m.Foo(12) 75 | ret2 := m.Foo(13) 76 | r.AssertEqual(10, ret1) 77 | r.AssertEqual(11, ret2) 78 | } 79 | 80 | func TestWhenSingleAnswerAndReturn(t *testing.T) { 81 | r := common.NewMockReporter(t) 82 | ctrl := NewMockController(r) 83 | m := Mock[whenSingleInterface](ctrl) 84 | WhenSingle(m.Foo(Any[int]())). 85 | ThenReturn(10). 86 | ThenAnswer(func(args []any) int { 87 | return args[0].(int) + 1 88 | }). 89 | ThenReturn(11) 90 | ret1 := m.Foo(12) 91 | ret2 := m.Foo(14) 92 | ret3 := m.Foo(15) 93 | r.AssertEqual(10, ret1) 94 | r.AssertEqual(15, ret2) 95 | r.AssertEqual(11, ret3) 96 | } 97 | 98 | func TestWhenSingleMockAsArg(t *testing.T) { 99 | r := common.NewMockReporter(t) 100 | ctrl := NewMockController(r) 101 | m := Mock[whenSingleInterface](ctrl) 102 | m2 := Mock[whenSingleMockAsArgInterface](ctrl) 103 | 104 | WhenSingle(m2.MockAsArg(Any[whenSingleInterface]())).ThenReturn(true) 105 | 106 | res := m2.MockAsArg(m) 107 | 108 | r.AssertEqual(true, res) 109 | r.AssertNoError() 110 | } 111 | -------------------------------------------------------------------------------- /tests/when/when_test.go: -------------------------------------------------------------------------------- 1 | package when 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ovechkin-dm/mockio/v2/tests/common" 7 | 8 | . "github.com/ovechkin-dm/mockio/v2/mock" 9 | ) 10 | 11 | type WhenInterface interface { 12 | Foo(a int) (int, string) 13 | Bar(a int, b string, c string) (int, string) 14 | NullableBar(a int, b string, c string) (*int, *string) 15 | Empty() int 16 | RespondWithMock() Nested 17 | RespondWithSlice() []int 18 | } 19 | 20 | type WhenInterface2 interface { 21 | Bar(a int, b string, c string) (int, string) 22 | } 23 | 24 | type Nested interface { 25 | Foo() int 26 | } 27 | 28 | type WhenStruct struct{} 29 | 30 | func (w *WhenStruct) foo() int { 31 | return 10 32 | } 33 | 34 | func TestWhenRet(t *testing.T) { 35 | r := common.NewMockReporter(t) 36 | ctrl := NewMockController(r) 37 | m := Mock[WhenInterface](ctrl) 38 | When(m.Foo(Any[int]())).ThenReturn(42, "test") 39 | i, s := m.Foo(10) 40 | r.AssertEqual(42, i) 41 | r.AssertEqual("test", s) 42 | } 43 | 44 | func TestEmptyWhenErr(t *testing.T) { 45 | defer func() { 46 | if r := recover(); r == nil { 47 | t.Errorf("Expected panic, but got none") 48 | } 49 | }() 50 | ws := &WhenStruct{} 51 | When(ws.foo()) 52 | } 53 | 54 | func TestIncorrectNumMatchers(t *testing.T) { 55 | r := common.NewMockReporter(t) 56 | ctrl := NewMockController(r) 57 | m := Mock[WhenInterface](ctrl) 58 | When(m.Bar(10, Any[string](), Any[string]())) 59 | r.AssertError() 60 | } 61 | 62 | func TestIncorrectMatchersReuse(t *testing.T) { 63 | r := common.NewMockReporter(t) 64 | ctrl := NewMockController(r) 65 | m := Mock[WhenInterface](ctrl) 66 | anyS := Any[string]() 67 | When(m.Bar(10, anyS, anyS)) 68 | r.AssertError() 69 | } 70 | 71 | func TestNoMatchersAreExactOnReturn(t *testing.T) { 72 | r := common.NewMockReporter(t) 73 | ctrl := NewMockController(r) 74 | m := Mock[WhenInterface](ctrl) 75 | When(m.Bar(10, "test1", "test2")).ThenReturn(10, "2") 76 | r.AssertNoError() 77 | i, s := m.Bar(10, "test1", "test2") 78 | r.AssertEqual(10, i) 79 | r.AssertEqual("2", s) 80 | } 81 | 82 | func TestIncorrectNumberReturnNullable(t *testing.T) { 83 | r := common.NewMockReporter(t) 84 | ctrl := NewMockController(r) 85 | m := Mock[WhenInterface](ctrl) 86 | When(m.NullableBar(10, "test1", "test2")).ThenReturn(nil) 87 | _, _ = m.NullableBar(10, "test1", "test2") 88 | r.AssertError() 89 | r.ErrorContains("invalid return values") 90 | } 91 | 92 | func TestNoMatchersAreExactOnAnswer(t *testing.T) { 93 | r := common.NewMockReporter(t) 94 | ctrl := NewMockController(r) 95 | m := Mock[WhenInterface](ctrl) 96 | When(m.Bar(10, "test1", "test2")).ThenAnswer(func(args []any) []any { 97 | return []any{args[0].(int) + 1, "2"} 98 | }) 99 | r.AssertNoError() 100 | i, s := m.Bar(10, "test1", "test2") 101 | r.AssertEqual(11, i) 102 | r.AssertEqual("2", s) 103 | } 104 | 105 | func TestEmptyArgs(t *testing.T) { 106 | r := common.NewMockReporter(t) 107 | ctrl := NewMockController(r) 108 | m := Mock[WhenInterface](ctrl) 109 | When(m.Empty()).ThenReturn(10) 110 | ret := m.Empty() 111 | r.AssertEqual(10, ret) 112 | } 113 | 114 | func TestWhenMultipleIfaces(t *testing.T) { 115 | r := common.NewMockReporter(t) 116 | ctrl := NewMockController(r) 117 | m1 := Mock[WhenInterface](ctrl) 118 | m2 := Mock[WhenInterface2](ctrl) 119 | When(m1.Bar(10, "test", "test")).ThenReturn(10, "test") 120 | When(m2.Bar(10, "test", "test")).ThenReturn(11, "test1") 121 | i1, s1 := m1.Bar(10, "test", "test") 122 | i2, s2 := m2.Bar(10, "test", "test") 123 | r.AssertEqual(10, i1) 124 | r.AssertEqual("test", s1) 125 | r.AssertEqual(11, i2) 126 | r.AssertEqual("test1", s2) 127 | r.AssertNoError() 128 | } 129 | 130 | func TestWhenWithinWhen(t *testing.T) { 131 | r := common.NewMockReporter(t) 132 | ctrl := NewMockController(r) 133 | m1 := Mock[WhenInterface](ctrl) 134 | When(m1.RespondWithMock()).ThenAnswer(func(args []any) []any { 135 | n := Mock[Nested](ctrl) 136 | WhenSingle(n.Foo()).ThenReturn(10) 137 | return []any{n} 138 | }) 139 | nested := m1.RespondWithMock() 140 | result := nested.Foo() 141 | r.AssertNoError() 142 | r.AssertEqual(10, result) 143 | } 144 | 145 | func TestSliceReturn(t *testing.T) { 146 | r := common.NewMockReporter(t) 147 | ctrl := NewMockController(r) 148 | m1 := Mock[WhenInterface](ctrl) 149 | expected := []int{1, 2, 3} 150 | When(m1.RespondWithSlice()).ThenReturn(expected) 151 | result := m1.RespondWithSlice() 152 | r.AssertEqual(expected, result) 153 | r.AssertNoError() 154 | } 155 | -------------------------------------------------------------------------------- /threadlocal/threadlocal.go: -------------------------------------------------------------------------------- 1 | package threadlocal 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/petermattis/goid" 7 | ) 8 | 9 | type ThreadLocal[T any] interface { 10 | Get() T 11 | Set(t T) 12 | Clear() 13 | } 14 | 15 | type impl[T any] struct { 16 | data sync.Map 17 | init func() T 18 | } 19 | 20 | func (i *impl[T]) Get() T { 21 | id := goid.Get() 22 | v, ok := i.data.Load(id) 23 | if !ok { 24 | nv := i.init() 25 | i.data.Store(id, nv) 26 | return nv 27 | } 28 | return v.(T) 29 | } 30 | 31 | func (i *impl[T]) Set(t T) { 32 | id := goid.Get() 33 | i.data.Store(id, t) 34 | } 35 | 36 | func (i *impl[T]) Clear() { 37 | id := goid.Get() 38 | i.data.Delete(id) 39 | } 40 | 41 | func NewThreadLocal[T any](initFunc func() T) ThreadLocal[T] { 42 | return &impl[T]{ 43 | data: sync.Map{}, 44 | init: initFunc, 45 | } 46 | } 47 | 48 | func GoId() int64 { 49 | return goid.Get() 50 | } 51 | --------------------------------------------------------------------------------