├── .github └── workflows │ ├── go.yml │ └── lint.yml ├── LICENSE ├── Makefile ├── README.md ├── go.mod ├── go.sum ├── iface └── interface.go ├── kit.go ├── kit_test.go ├── mockimpl ├── es.go ├── http.go ├── interface.go ├── mysql.go └── redis.go └── paramter.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | env: 13 | CI: test 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Set up Go 18 | uses: actions/setup-go@v2 19 | with: 20 | go-version: 1.16 21 | 22 | - name: Build 23 | run: go build -v ./... 24 | 25 | - name: Format 26 | run: if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then exit 1; fi 27 | 28 | - name: Test 29 | run: go test -v ./... 30 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | branches: [ main ] 5 | pull_request: 6 | branches: [ main ] 7 | jobs: 8 | golangci: 9 | name: lint 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: golangci-lint 14 | uses: golangci/golangci-lint-action@v2 15 | with: 16 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 17 | version: v1.29 18 | args: --timeout=10m -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 SHIHUO 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. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | fmt: 2 | @gofmt -s -w ./ 3 | lint: 4 | @golangci-lint run -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mockit 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/shihuo-cn/mockit)](https://goreportcard.com/report/github.com/shihuo-cn/mockit) 3 | ![Go](https://github.com/shihuo-cn/mockit/workflows/Go/badge.svg) 4 | ## 目标:将mock变得简单,让代码维护变得容易 5 | ## 分支介绍 6 | - main 主分支,覆盖了单元测试 7 | - light 轻分支,去除了单元测试,简化了依赖项,方便其他团队使用 8 | ## 常见Mock难点 9 | - 不同中间件,mock库设计模式不一致,学习代价高,差异化明显 10 | - mock方案强依赖服务端,无法灵活解耦 11 | - 单元测试适配各种中间件的方案后,依赖管理杂乱 12 | - 综上所述不想写mock,也没有良好的可执行方案,放弃单测 13 | ## mockit做到了什么 14 | - 统一简化语法 15 | - 无需服务端 16 | - 解耦依赖项 17 | - testMain统一管理 18 | 19 | ## mockit 使用 20 | ### 目前支持 21 | - `Redis`,`MySQL`,`Interface`,`HTTP` 22 | - `GRPC` 可以使用proto生成interface使用`Interface`模拟 23 | - `ElasticSearch` 24 | + 使用`HTTP`方式代理client,不过es的返回值比较复杂,请求路径没有普通HTTP直观 25 | + 使用`Interface`方式,将`dao`层抽象成接口方式,这种方式下,接口返回值模拟相对方便直观 26 | 27 | > 理论上业务抽象使用`Interface`方式都可达成 28 | 29 | ### 准备 (具体参照kit_test.go) 30 | 1. interface生成 https://github.com/golang/mock 31 | ```golang 32 | // interface生成方式 33 | $ mockgen -source ./iface/interface.go -package mockimpl -destination ./mockimpl/interface.go 34 | // 而后将以下new方法的返回值改成interface{} 35 | before: func NewMockDemoInterface(ctrl *gomock.Controller) *MockDemoInterface { 36 | after: func NewMockDemoInterface(ctrl *gomock.Controller) interface{}} { 37 | mock := &MockDemoInterface{ctrl: ctrl} 38 | mock.recorder = &MockDemoInterfaceMockRecorder{mock} 39 | return mock 40 | } 41 | ``` 42 | 2. sqlmock依赖replace 43 | > 目前需要替换下sqlmock库,目前pr还在合并中,预计最近2周就能OK 44 | ``` 45 | replace github.com/DATA-DOG/go-sqlmock v1.5.0 => github.com/Rennbon/go-sqlmock v1.5.1-0.20211212104631-9c4a20760689 46 | ``` 47 | ### mockit自身单测 48 | - 当前目录下 `iface` 中有4个方法, `mockimpl`中分别为各个mock实例的实现 49 | - kit_test.go中`mockSrv`引用了这些`mockimpl`实例,在`testMain`中列举了mock的启动方式,并且在之后所有的Test中介绍了如何使用 50 | 51 | ## 引用传送 52 | - https://github.com/DATA-DOG/go-sqlmock 53 | - https://github.com/jarcoal/httpmock 54 | - https://github.com/golang/mock 55 | - https://github.com/alicebob/miniredis 56 | - https://github.com/go-redis/redis 57 | - https://github.com/olivere/elastic 58 | - https://github.com/go-gorm/gorm 59 | 60 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/shihuo-cn/mockit 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/DATA-DOG/go-sqlmock v1.5.0 7 | github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect 8 | github.com/alicebob/miniredis v2.5.0+incompatible 9 | github.com/go-redis/redis/v8 v8.11.4 10 | github.com/golang/mock v1.6.0 11 | github.com/gomodule/redigo v1.8.6 // indirect 12 | github.com/jarcoal/httpmock v1.0.8 13 | github.com/olivere/elastic/v7 v7.0.29 14 | github.com/pkg/errors v0.9.1 15 | github.com/sirupsen/logrus v1.8.1 16 | github.com/stretchr/testify v1.6.1 17 | github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect 18 | gorm.io/driver/mysql v1.2.1 19 | gorm.io/gorm v1.22.4 20 | ) 21 | 22 | replace github.com/DATA-DOG/go-sqlmock v1.5.0 => github.com/Rennbon/go-sqlmock v1.5.1-0.20211212104631-9c4a20760689 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/Rennbon/go-sqlmock v1.5.1-0.20211212104631-9c4a20760689 h1:VrH+SUsr4aDD9KbuDRbr4QeRafIOGOs55MMgSML8iBc= 4 | github.com/Rennbon/go-sqlmock v1.5.1-0.20211212104631-9c4a20760689/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= 5 | github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= 6 | github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= 7 | github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI= 8 | github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= 9 | github.com/aws/aws-sdk-go v1.40.43/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= 10 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 11 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= 12 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 13 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 14 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 15 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 16 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 17 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 18 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 19 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 20 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 21 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 22 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 23 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 24 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 25 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 26 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 27 | github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= 28 | github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= 29 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 30 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 31 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 32 | github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= 33 | github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= 34 | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= 35 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 36 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 37 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 38 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 39 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 40 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 41 | github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= 42 | github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 43 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 44 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 45 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 46 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 47 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 48 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 49 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 50 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 51 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 52 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 53 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 54 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 55 | github.com/gomodule/redigo v1.8.6 h1:h7kHSqUl2kxeaQtVslsfUCPJ1oz2pxcyzLy4zezIzPw= 56 | github.com/gomodule/redigo v1.8.6/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= 57 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 58 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 59 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 60 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 61 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 62 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 63 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 64 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 65 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 66 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 67 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 68 | github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k= 69 | github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= 70 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 71 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 72 | github.com/jinzhu/now v1.1.3 h1:PlHq1bSCSZL9K0wUhbm2pGLoTWs2GwVhsP6emvGV/ZI= 73 | github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 74 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 75 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 76 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 77 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 78 | github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= 79 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 80 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 81 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 82 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 83 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 84 | github.com/olivere/elastic/v7 v7.0.29 h1:zvorjSPHFli/0owqfoLq0ZOtVhZSyHsMbRi29Vj7T14= 85 | github.com/olivere/elastic/v7 v7.0.29/go.mod h1:8PlkMD2Xb690IPhIPii2SypuuXtXX3dDcSKGqnEGXzE= 86 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 87 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 88 | github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= 89 | github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= 90 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 91 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 92 | github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= 93 | github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= 94 | github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 95 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 96 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 97 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 98 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 99 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 100 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 101 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 102 | github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= 103 | github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= 104 | github.com/smartystreets/gunit v1.4.2/go.mod h1:ZjM1ozSIMJlAz/ay4SG8PeKF00ckUp+zMHZXV9/bvak= 105 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 106 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 107 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 108 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 109 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 110 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 111 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 112 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 113 | github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw= 114 | github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= 115 | go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= 116 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 117 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 118 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 119 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 120 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 121 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 122 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 123 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 124 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 125 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 126 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 127 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 128 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 129 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 130 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 131 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 132 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 133 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 134 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 135 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 136 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 137 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= 138 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 139 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 140 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 141 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 142 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 143 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 144 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 145 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 146 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 147 | golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 148 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 149 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 150 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 151 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 152 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 153 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 154 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 155 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 156 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 157 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 158 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 159 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 160 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= 161 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 162 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 163 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 164 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 165 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 166 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 167 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 168 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 169 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 170 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 171 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 172 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 173 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 174 | golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 175 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 176 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 177 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 178 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 179 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 180 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 181 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 182 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 183 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 184 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 185 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 186 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 187 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 188 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 189 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 190 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 191 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 192 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 193 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 194 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 195 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 196 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 197 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 198 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 199 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 200 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 201 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 202 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 203 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 204 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 205 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 206 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 207 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 208 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 209 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 210 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 211 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 212 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 213 | gorm.io/driver/mysql v1.2.1 h1:h+3f1l9Ng2C072Y2tIiLgPpWN78r1KXL7bHJ0nTjlhU= 214 | gorm.io/driver/mysql v1.2.1/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo= 215 | gorm.io/gorm v1.22.4 h1:8aPcyEJhY0MAt8aY6Dc524Pn+pO29K+ydu+e/cXSpQM= 216 | gorm.io/gorm v1.22.4/go.mod h1:1aeVC+pe9ZmvKZban/gW4QPra7PRoTEssyc922qCAkk= 217 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 218 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 219 | -------------------------------------------------------------------------------- /iface/interface.go: -------------------------------------------------------------------------------- 1 | package iface 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | // DemoInterface 全局接口,所有实现的中间件或者其他都需遵循这个接口实现mock 9 | type DemoInterface interface { 10 | First(ctx context.Context, key int64) (int64, error) 11 | Put(ctx context.Context, key, val int64) error 12 | List(ctx context.Context, relationId int64, pageIndex, pageSize int) ([]*KV, error) 13 | Aggregate(ctx context.Context, dm *DemoInterfaceModel, time time.Time) ([]*DemoInterfaceModel, int, error) 14 | } 15 | 16 | type DemoType uint 17 | type DemoInterfaceModel struct { 18 | Type DemoType 19 | Date time.Time 20 | Int int 21 | Float float64 22 | Str string 23 | Slice []string 24 | Slice2 []int 25 | Struct struct { 26 | Name string 27 | Value int 28 | } 29 | Err error 30 | InnerModel *DemoInnerModel 31 | InnerModelSlice []*DemoInnerModel 32 | } 33 | 34 | type DemoInnerModel struct { 35 | One int 36 | Two string 37 | Three time.Time 38 | Four error 39 | } 40 | 41 | type KV struct { 42 | Id int64 `gorm:"column:id;primary_key" json:"id" mock:"id"` 43 | Key int64 `gorm:"column:key" json:"key" mock:"key"` 44 | Val int64 `gorm:"column:val" json:"val" mock:"val"` 45 | Name string `gorm:"column:name" json:"name" mock:"name"` 46 | } 47 | -------------------------------------------------------------------------------- /kit.go: -------------------------------------------------------------------------------- 1 | package mockit 2 | 3 | import ( 4 | "database/sql" 5 | "database/sql/driver" 6 | "github.com/DATA-DOG/go-sqlmock" 7 | "github.com/alicebob/miniredis" 8 | "github.com/golang/mock/gomock" 9 | "github.com/jarcoal/httpmock" 10 | "github.com/pkg/errors" 11 | log "github.com/sirupsen/logrus" 12 | "net/http" 13 | "reflect" 14 | "testing" 15 | "unsafe" 16 | ) 17 | 18 | // Mockit mock工具集,集成如下 19 | // 1. http (elasticSearch) 20 | // 2. MYSQL (支持多类sql,目前支持了mysql,如需添加请联系) 21 | // 3. Redis 22 | // 4. interface (GRPC) 23 | // NOTE: 需结合https://github.com/golang/mock使用,生成后需要将实例化返回值改为interface{},如: NewMockXXX(ctrl *gomock.Controller) interface{} 24 | type Mockit interface { 25 | // GetInterfaceClient 获取interface的mockClient,获取后 m.(*MockXX)获得实例 26 | GetInterfaceClient(name string) interface{} 27 | // MysqlExecExpect mysql 增删改使用 28 | MysqlExecExpect(ep ExpectParam, tb testing.TB) 29 | // MysqlQueryExpect mysql 查询使用 30 | MysqlQueryExpect(ep ExpectParam, tb testing.TB) 31 | // InterfaceExpect interface mockgen 生成使用 32 | // NOTE: grpc 推荐mockgen生成后使用此方案作为mock代理 33 | InterfaceExpect(ep ExpectParam, tb testing.TB) 34 | // HttpExpect http client 拦截使用 35 | // NOTE: ES也是此方案 36 | HttpExpect(ep ExpectParam, tb testing.TB, httpStatus ...int) 37 | // BeforeTest NOTE:清洁单元测试环境 38 | BeforeTest() 39 | // AfterTest NOTE:配合BeforeTest 40 | AfterTest() 41 | // SqlDB 获取sql conn 42 | SqlDB() *sql.DB 43 | // InterceptHttpClient 拦截http client 44 | InterceptHttpClient(client *http.Client) 45 | 46 | // RedisAddr 获取伪redis server addr 47 | RedisAddr() string 48 | } 49 | 50 | type mockit struct { 51 | sqlEx sqlmock.Sqlmock // sql excepted 52 | redisSrv *miniredis.Miniredis // redis server 53 | sqlDB *sql.DB 54 | ormTag string // 实体解析的tag 55 | clientInitFs []MockClientInitFunc 56 | iFaceClients map[string]interface{} 57 | iFaceValue map[string]reflect.Value 58 | } 59 | 60 | type MockClientInitFunc func(ctrl *gomock.Controller) interface{} 61 | 62 | // NewWithMockTag 创建mock工具包 63 | // @ormTag: mysql 映射的实体tag,如下可使用"mock"作为tag数据库映射, 64 | // NOTE: gorm中可以带primary key;varchar等,请自定义tag使用 65 | // type Kit struct { 66 | // Name string "mock:"name" 67 | // } 68 | // @fs: 使用mockgen生成的interface mock client 69 | // NOTE: 一般默认生成的是 NewMockDemoInterface(ctrl *gomock.Controller) *MockDemoInterface 70 | // TODO: 需要修改成 NewMockDemoInterface(ctrl *gomock.Controller) interface{}后传入,方可拦截代理 71 | func NewWithMockTag(tag string, fs ...MockClientInitFunc) (Mockit, error) { 72 | kit := new(mockit) 73 | kit.iFaceClients = make(map[string]interface{}) 74 | kit.iFaceValue = make(map[string]reflect.Value) 75 | kit.clientInitFs = fs 76 | kit.ormTag = tag 77 | sqlDB, mock, err := sqlmock.New() 78 | if err != nil { 79 | return nil, errors.WithStack(err) 80 | } 81 | kit.sqlDB = sqlDB 82 | kit.sqlEx = mock 83 | // redis 84 | kit.redisSrv, err = miniredis.Run() 85 | if err != nil { 86 | return nil, errors.WithStack(err) 87 | } 88 | // 初始化interface mock 并通过内部反射解耦原库使用 89 | 90 | t := log.New() 91 | t.SetFormatter(&log.TextFormatter{ 92 | DisableColors: true, 93 | FullTimestamp: true, 94 | }) 95 | kit.initGoMockInterface(t) 96 | // 启用httpmock拦截 97 | httpmock.Activate() 98 | return kit, nil 99 | } 100 | 101 | // New 创建mock工具包 102 | // @fs: 使用mockgen生成的interface mock client 103 | // NOTE: 一般默认生成的是 NewMockDemoInterface(ctrl *gomock.Controller) *MockDemoInterface 104 | // TODO: 需要修改成 NewMockDemoInterface(ctrl *gomock.Controller) interface{}后传入,方可拦截代理 105 | func New(fs ...MockClientInitFunc) (Mockit, error) { 106 | return NewWithMockTag("mock", fs...) 107 | } 108 | 109 | type argsKind uint8 110 | 111 | const ( 112 | notSet argsKind = 0 113 | normal argsKind = 1 114 | byIndex argsKind = 2 115 | ) 116 | 117 | func (mk *mockit) GetInterfaceClient(name string) interface{} { 118 | if mk == nil { 119 | return nil 120 | } 121 | return mk.iFaceClients[name] 122 | } 123 | 124 | func (mk *mockit) MysqlExecExpect(ep ExpectParam, tb testing.TB) { 125 | p := ep.(*expectParam) 126 | if len(p.method) == 0 { 127 | tb.Fatal("there is no SQL statements in the method filed") 128 | } 129 | if len(p.returns) != 2 { 130 | tb.Fatal("returns requires the addition of lastInsertId and rowsAffected, and the type is int64") 131 | } 132 | var num1, num2 int64 133 | for k, v := range p.returns { 134 | var numTmp int64 135 | switch tmp := v.(type) { 136 | case uint: 137 | numTmp = int64(tmp) 138 | case int: 139 | numTmp = int64(tmp) 140 | case int64: 141 | numTmp = int64(tmp) 142 | case int32: 143 | numTmp = int64(tmp) 144 | case uint32: 145 | numTmp = int64(tmp) 146 | case uint64: 147 | numTmp = int64(tmp) 148 | default: 149 | tb.Fatal("the returns type must be int of the func mysqlExecExpect") 150 | } 151 | if k == 0 { 152 | num1 = numTmp 153 | } else { 154 | num2 = numTmp 155 | } 156 | } 157 | var args []driver.Value 158 | for _, v := range p.args { 159 | args = append(args, v) 160 | } 161 | mk.sqlEx.ExpectBegin() 162 | mk.sqlEx.ExpectExec(p.method).WithArgs(args...).WillReturnResult(sqlmock.NewResult(num1, num2)) 163 | mk.sqlEx.ExpectCommit() 164 | } 165 | 166 | var errorInterface = reflect.TypeOf((*error)(nil)).Elem() 167 | 168 | func (mk *mockit) MysqlQueryExpect(ep ExpectParam, tb testing.TB) { 169 | var ( 170 | rows *sqlmock.Rows 171 | errExpected bool 172 | err error 173 | ) 174 | p := ep.(*expectParam) 175 | if len(p.method) == 0 { 176 | tb.Fatal("there is no SQL statements in the method filed") 177 | } 178 | if len(p.key) > 0 { 179 | rows = sqlmock.NewRows([]string{p.key}).AddRow(p.val) 180 | } else { 181 | if len(p.returns) != 1 { 182 | tb.Fatal("the query must return one result") 183 | } 184 | 185 | typ := reflect.TypeOf(p.returns[0]) 186 | if typ.Implements(errorInterface) { 187 | errExpected = true 188 | } else { 189 | rows, err = sqlmock.NewRowsFromInterface(p.returns[0], mk.ormTag) 190 | } 191 | } 192 | if err != nil { 193 | tb.Fatalf("new rows failed:%s", err) 194 | } 195 | var args []driver.Value 196 | for _, v := range p.args { 197 | args = append(args, v) 198 | } 199 | if errExpected { 200 | mk.sqlEx.ExpectQuery(p.method).WithArgs(args...).WillReturnError(p.returns[0].(error)) 201 | } else { 202 | mk.sqlEx.ExpectQuery(p.method).WithArgs(args...).WillReturnRows(rows) 203 | } 204 | } 205 | 206 | func (mk *mockit) InterfaceExpect(ep ExpectParam, tb testing.TB) { 207 | p := ep.(*expectParam) 208 | if len(p.path) == 0 || len(p.method) == 0 { 209 | tb.Fatalf("both path and method are required") 210 | } 211 | cli, exists := mk.iFaceValue[p.path] 212 | if !exists { 213 | tb.Fatalf("interface mock client %s not exists", p.path) 214 | } 215 | method := cli.Elem().MethodByName(p.method) 216 | if !method.IsValid() { 217 | tb.Fatalf("path:%s method:%s not exists", p.path, p.method) 218 | } 219 | numIn := method.Type().NumIn() 220 | inputs := make([]reflect.Value, numIn) 221 | 222 | switch p.argsKind { 223 | case normal: 224 | for i, arg := range p.args { 225 | inputs[i] = reflect.ValueOf(arg) 226 | } 227 | case byIndex: 228 | for i := 0; i < numIn; i++ { 229 | if arg, exists := p.idxArgs[i]; exists { 230 | inputs[i] = reflect.ValueOf(gomock.Eq(arg)) 231 | } else { 232 | inputs[i] = reflect.ValueOf(gomock.Any()) 233 | } 234 | } 235 | default: 236 | for i := 0; i < numIn; i++ { 237 | inputs[i] = reflect.ValueOf(gomock.Any()) 238 | } 239 | } 240 | outputs := method.Call(inputs) 241 | if len(outputs) != 1 { 242 | tb.Fatal("method returns not match") 243 | } 244 | methodReturn := outputs[0].MethodByName("Return") 245 | originalMethod, exists := reflect.TypeOf(mk.iFaceClients[p.path]).MethodByName(p.method) 246 | if !exists { 247 | tb.Fatal("mock client is not formatted correctly") 248 | } 249 | var types []reflect.Type 250 | for i := 0; i < originalMethod.Type.NumOut(); i++ { 251 | types = append(types, originalMethod.Type.Out(i)) 252 | } 253 | rs := reflect.New(reflect.TypeOf([]interface{}{})).Elem() 254 | for i, v := range p.returns { 255 | val := reflect.ValueOf(v) 256 | if val.IsValid() { 257 | rs = reflect.Append(rs, val) 258 | } else { 259 | rs = reflect.Append(rs, reflect.Zero(types[i])) 260 | } 261 | } 262 | methodReturn.CallSlice([]reflect.Value{rs}) 263 | } 264 | 265 | func (mk *mockit) HttpExpect(ep ExpectParam, tb testing.TB, httpStatus ...int) { 266 | var ( 267 | resp httpmock.Responder 268 | err error 269 | ) 270 | p := ep.(*expectParam) 271 | if len(p.path) == 0 || len(p.method) == 0 { 272 | tb.Fatalf("both path and method are required") 273 | } 274 | status := http.StatusOK 275 | if len(httpStatus) > 0 { 276 | status = httpStatus[0] 277 | } 278 | switch p.key { 279 | case HttpResponseFunc: 280 | if p.responseHandler == nil { 281 | tb.Fatal("responseHandler is nil") 282 | } 283 | resp = p.responseHandler 284 | case HttpResponseString: 285 | str, ok := p.val.(string) 286 | if !ok { 287 | tb.Fatal("return val must be string type") 288 | } 289 | resp = httpmock.NewStringResponder(status, str) 290 | case HttpResponseJson: 291 | resp, err = httpmock.NewJsonResponder(status, p.val) 292 | if err != nil { 293 | tb.Fatalf("NewJsonResponder failed:%s", err) 294 | } 295 | default: 296 | if len(p.returns) != 1 { 297 | tb.Fatal("response nil, please set response via any one of WithKeyValReturn/WithHttpResponseHandler/WithReturns") 298 | } 299 | arg := p.returns[0] 300 | switch argTmp := arg.(type) { 301 | case string: 302 | resp = httpmock.NewStringResponder(status, argTmp) 303 | default: 304 | resp, err = httpmock.NewJsonResponder(status, arg) 305 | if err != nil { 306 | tb.Fatalf("NewJsonResponder failed:%s", err) 307 | } 308 | } 309 | } 310 | httpmock.RegisterResponder( 311 | p.method, 312 | p.path, 313 | resp, 314 | ) 315 | } 316 | 317 | // 初始化interface mock 并通过内部反射解耦原库使用 318 | func (mk *mockit) initGoMockInterface(t gomock.TestReporter) { 319 | ctrl := gomock.NewController(t) 320 | for _, f := range mk.clientInitFs { 321 | cli := f(ctrl) 322 | val := reflect.ValueOf(cli) 323 | typ := val.Elem().Type() 324 | if typ.Kind() != reflect.Struct { 325 | panic("mock client must gen form mockgen") 326 | } 327 | cliName := typ.Name() 328 | mk.iFaceClients[cliName] = cli 329 | expect := val.Elem().FieldByName("recorder") 330 | unsafeExpect := reflect.NewAt(expect.Type(), unsafe.Pointer(expect.UnsafeAddr())) 331 | mk.iFaceValue[cliName] = unsafeExpect 332 | } 333 | } 334 | 335 | func (mk *mockit) BeforeTest() { 336 | httpmock.Activate() 337 | } 338 | 339 | func (mk *mockit) AfterTest() { 340 | httpmock.DeactivateAndReset() 341 | } 342 | 343 | func (mk *mockit) InterceptHttpClient(client *http.Client) { 344 | httpmock.ActivateNonDefault(client) 345 | } 346 | 347 | func (mk *mockit) RedisAddr() string { 348 | if mk == nil { 349 | return "" 350 | } 351 | return mk.redisSrv.Addr() 352 | } 353 | 354 | func (mk *mockit) SqlDB() *sql.DB { 355 | if mk == nil { 356 | return nil 357 | } 358 | return mk.sqlDB 359 | } 360 | -------------------------------------------------------------------------------- /kit_test.go: -------------------------------------------------------------------------------- 1 | package mockit 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "github.com/jarcoal/httpmock" 7 | "github.com/shihuo-cn/mockit/iface" 8 | "github.com/shihuo-cn/mockit/mockimpl" 9 | "github.com/stretchr/testify/assert" 10 | "gorm.io/driver/mysql" 11 | "gorm.io/gorm" 12 | "net/http" 13 | "os" 14 | "testing" 15 | "time" 16 | ) 17 | 18 | // 以下所有方式都实现了 mockglobal.DemoInterface 19 | // NOTE: 这里对于使用Interface或者struct无强制要求,2者都支持 20 | type mockSrv struct { 21 | es *mockimpl.MockEs // struct 方式 22 | httpCli *mockimpl.MockHttp // struct 方式 23 | iface iface.DemoInterface // interface方式 24 | redis *mockimpl.MockRedis // struct 方式 25 | mysql iface.DemoInterface // interface方式 26 | } 27 | 28 | var ( 29 | kit Mockit 30 | ctx = context.Background() 31 | srv *mockSrv 32 | ) 33 | 34 | func TestMain(m *testing.M) { 35 | var err error 36 | // interface intercept agent 37 | // 初始化kit 38 | kit, err = New(mockimpl.NewMockDemoInterface) 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | // http client 拦截 44 | { // 初始化mock service 45 | cli := http.DefaultClient 46 | kit.InterceptHttpClient(cli) 47 | mockInterface := kit.GetInterfaceClient("MockDemoInterface") 48 | iface := mockInterface.(*mockimpl.MockDemoInterface) 49 | // gorm2 io 50 | gormDB, err := gorm.Open(mysql.New(mysql.Config{ 51 | Conn: kit.SqlDB(), 52 | SkipInitializeWithVersion: true, 53 | }), &gorm.Config{}) 54 | if err != nil { 55 | panic(err) 56 | } 57 | // 拦截 58 | srv = &mockSrv{ 59 | es: mockimpl.NewMockEs(cli), 60 | httpCli: mockimpl.NewMockHttp(cli), 61 | iface: iface, 62 | redis: mockimpl.NewMockRedis(kit.RedisAddr()), 63 | mysql: mockimpl.NewSqlDao(gormDB), 64 | } 65 | } 66 | os.Exit(m.Run()) 67 | } 68 | 69 | func TestMockInterface(t *testing.T) { 70 | t.Run("easy test", func(t *testing.T) { 71 | var ( 72 | arg = int64(1) 73 | outExpected = int64(20) 74 | errExpected = errors.New("this is error") 75 | ) 76 | param := NewExpectParam(). 77 | WithMethod("First"). 78 | WithPath("MockDemoInterface"). 79 | WithArgs(ctx, arg). 80 | WithReturns(outExpected, errExpected) 81 | kit.InterfaceExpect(param, t) 82 | outTmp, errTmp := srv.iface.First(ctx, arg) 83 | assert.EqualValues(t, errExpected, errTmp) 84 | assert.EqualValues(t, outExpected, outTmp) 85 | }) 86 | t.Run("complex test", func(t *testing.T) { 87 | var ( 88 | aggTime = time.Now() 89 | aggExcepted = &iface.DemoInterfaceModel{ 90 | Type: 1, 91 | Date: time.Now(), 92 | Int: 2, 93 | Float: 3.75, 94 | Str: "this is string", 95 | Slice: []string{"str1", "str2"}, 96 | Slice2: []int{1, 2}, 97 | Struct: struct { 98 | Name string 99 | Value int 100 | }{ 101 | Name: "name", 102 | Value: 100, 103 | }, 104 | Err: errors.New("this is error"), 105 | InnerModel: &iface.DemoInnerModel{ 106 | One: 0, 107 | }, 108 | InnerModelSlice: []*iface.DemoInnerModel{ 109 | { 110 | One: 1, 111 | Two: "two", 112 | Three: time.Now(), 113 | Four: errors.New("four"), 114 | }, 115 | { 116 | One: 11, 117 | Two: "two too", 118 | Three: time.Now().AddDate(1, 0, 0), 119 | Four: errors.New("four too"), 120 | }, 121 | }, 122 | } 123 | return1Excepted = []*iface.DemoInterfaceModel{aggExcepted, aggExcepted, aggExcepted} 124 | return2Excepted = 2222 125 | return3Excepted = errors.New("complex test") 126 | ) 127 | 128 | param2 := NewExpectParam(). 129 | WithMethod("Aggregate"). 130 | WithPath("MockDemoInterface"). 131 | WithArgsByIndex(1, aggExcepted). 132 | WithArgsByIndex(2, aggTime). 133 | WithReturns(return1Excepted, return2Excepted, return3Excepted) 134 | kit.InterfaceExpect(param2, t) 135 | 136 | re1, re2, re3 := srv.iface.Aggregate(ctx, aggExcepted, aggTime) 137 | assert.EqualValues(t, return1Excepted, re1) 138 | assert.EqualValues(t, return2Excepted, re2) 139 | assert.EqualValues(t, return3Excepted, re3) 140 | }) 141 | } 142 | 143 | func TestMockMySql(t *testing.T) { 144 | t.Run("insert", func(t *testing.T) { 145 | p := NewExpectParam(). 146 | WithMethod("INSERT"). 147 | WithReturns(1, 1) 148 | kit.MysqlExecExpect(p, t) 149 | err := srv.mysql.Put(ctx, 1, 2) 150 | assert.Nil(t, err) 151 | }) 152 | t.Run("insert with args", func(t *testing.T) { 153 | // INSERT INTO `demo` (`key`,`val`,`name`) VALUES (?,?,?) 154 | arg1 := int64(5) 155 | arg2 := int64(10) 156 | p := NewExpectParam(). 157 | WithMethod("^INSERT INTO `demo` \\(`key`,`val`,`name`\\) VALUES (.+)"). 158 | WithArgs(arg1, arg2, "name"). 159 | WithReturns(1, 1) 160 | kit.MysqlExecExpect(p, t) 161 | err := srv.mysql.Put(ctx, arg1, arg2) 162 | assert.Nil(t, err) 163 | }) 164 | t.Run("select count", func(t *testing.T) { 165 | expectCount := int64(20) 166 | arg := int64(10) 167 | p := NewExpectParam(). 168 | WithMethod("SELECT COUNT\\(1\\) FROM `demo` WHERE key = (.+)"). 169 | WithArgs(arg). 170 | WithKeyValReturn("COUNT", expectCount) 171 | kit.MysqlQueryExpect(p, t) 172 | count, err := srv.mysql.First(ctx, arg) 173 | assert.Nil(t, err) 174 | assert.Equal(t, expectCount, count) 175 | }) 176 | 177 | t.Run("select list", func(t *testing.T) { 178 | relationId := int64(20) 179 | pageIndex := 2 180 | pageSize := 20 181 | expectedReturns := []*iface.KV{ 182 | { 183 | Id: 1, 184 | Key: 1, 185 | Val: 100, 186 | Name: "k1", 187 | }, 188 | { 189 | Id: 2, 190 | Key: 2, 191 | Val: 200, 192 | Name: "k2", 193 | }, 194 | } 195 | p := NewExpectParam(). 196 | WithMethod("SELECT (.+) FROM `demo` WHERE relation_id = (.+)"). 197 | WithArgs(relationId, false). 198 | WithReturns(expectedReturns) 199 | kit.MysqlQueryExpect(p, t) 200 | res, err := srv.mysql.List(ctx, relationId, pageIndex, pageSize) 201 | assert.Nil(t, err) 202 | assert.EqualValues(t, expectedReturns, res) 203 | }) 204 | t.Run("error return", func(t *testing.T) { 205 | relationId := int64(200) 206 | pageIndex := 2 207 | pageSize := 20 208 | errExpected := gorm.ErrRecordNotFound 209 | p := NewExpectParam(). 210 | WithMethod("SELECT (.+) FROM `demo` WHERE relation_id = (.+)"). 211 | WithArgs(relationId, false). 212 | WithReturns(errExpected) 213 | kit.MysqlQueryExpect(p, t) 214 | res, err := srv.mysql.List(ctx, relationId, pageIndex, pageSize) 215 | assert.Equal(t, errExpected, err) 216 | assert.Nil(t, res) 217 | }) 218 | } 219 | 220 | func TestMockRedis(t *testing.T) { 221 | keyExcepted := int64(10) 222 | valExcepted := int64(20) 223 | err := srv.redis.Put(ctx, keyExcepted, valExcepted) 224 | assert.Nil(t, err) 225 | val, err := srv.redis.First(ctx, keyExcepted) 226 | assert.Nil(t, err) 227 | assert.Equal(t, valExcepted, val) 228 | } 229 | 230 | func TestMockHttp(t *testing.T) { 231 | t.Run("get string response with kv", func(t *testing.T) { 232 | p := NewExpectParam(). 233 | WithMethod("GET"). 234 | WithPath("http://mockhttp.com/first"). 235 | WithKeyValReturn(HttpResponseString, "20") 236 | kit.HttpExpect(p, t) 237 | num, err := srv.httpCli.First(ctx, 10) 238 | assert.Nil(t, err) 239 | assert.Equal(t, int64(20), num) 240 | }) 241 | t.Run("get string response with returns", func(t *testing.T) { 242 | p := NewExpectParam(). 243 | WithMethod("GET"). 244 | WithPath("http://mockhttp.com/first"). 245 | WithReturns("20") 246 | kit.HttpExpect(p, t) 247 | num, err := srv.httpCli.First(ctx, 10) 248 | assert.Nil(t, err) 249 | assert.Equal(t, int64(20), num) 250 | }) 251 | t.Run("list json response with kv", func(t *testing.T) { 252 | expectResponse := []*iface.KV{ 253 | { 254 | Id: 1, 255 | Key: 10, 256 | Val: 100, 257 | Name: "k1", 258 | }, 259 | { 260 | Id: 2, 261 | Key: 20, 262 | Val: 200, 263 | Name: "k2", 264 | }, 265 | } 266 | p := NewExpectParam(). 267 | WithMethod("GET"). 268 | WithPath("http://mockhttp.com/list"). 269 | WithKeyValReturn(HttpResponseJson, expectResponse) 270 | kit.HttpExpect(p, t) 271 | list, err := srv.httpCli.List(ctx, 10, 20, 40) 272 | assert.Nil(t, err) 273 | assert.EqualValues(t, expectResponse, list) 274 | }) 275 | t.Run("list json response with returns", func(t *testing.T) { 276 | expectResponse := []*iface.KV{ 277 | { 278 | Id: 1, 279 | Key: 10, 280 | Val: 100, 281 | Name: "k1", 282 | }, 283 | { 284 | Id: 2, 285 | Key: 20, 286 | Val: 200, 287 | Name: "k2", 288 | }, 289 | } 290 | p := NewExpectParam(). 291 | WithMethod("GET"). 292 | WithPath("http://mockhttp.com/list"). 293 | WithReturns(expectResponse) 294 | kit.HttpExpect(p, t) 295 | list, err := srv.httpCli.List(ctx, 10, 20, 40) 296 | assert.Nil(t, err) 297 | assert.EqualValues(t, expectResponse, list) 298 | }) 299 | 300 | t.Run("list json response with func", func(t *testing.T) { 301 | expectResponse1 := []*iface.KV{ 302 | { 303 | Id: 1, 304 | Key: 10, 305 | Val: 100, 306 | Name: "k1", 307 | }, 308 | { 309 | Id: 2, 310 | Key: 20, 311 | Val: 200, 312 | Name: "k2", 313 | }, 314 | } 315 | expectResponse2 := []*iface.KV{ 316 | { 317 | Id: 11, 318 | Key: 110, 319 | Val: 1100, 320 | Name: "k11", 321 | }, 322 | { 323 | Id: 12, 324 | Key: 120, 325 | Val: 1200, 326 | Name: "k12", 327 | }, 328 | } 329 | p := NewExpectParam(). 330 | WithMethod("GET"). 331 | WithPath("http://mockhttp.com/list"). 332 | WithHttpResponseFunc(func(req *http.Request) (*http.Response, error) { 333 | rid := req.URL.Query().Get("relationId") 334 | var resp []*iface.KV 335 | switch rid { 336 | case "1": 337 | resp = expectResponse1 338 | case "2": 339 | resp = expectResponse2 340 | default: 341 | resp = make([]*iface.KV, 0) 342 | } 343 | return httpmock.NewJsonResponse(http.StatusOK, resp) 344 | }) 345 | kit.HttpExpect(p, t) 346 | list1, err := srv.httpCli.List(ctx, 1, 20, 40) 347 | assert.Nil(t, err) 348 | assert.EqualValues(t, list1, expectResponse1) 349 | list2, err := srv.httpCli.List(ctx, 2, 20, 40) 350 | assert.Nil(t, err) 351 | assert.EqualValues(t, list2, expectResponse2) 352 | }) 353 | 354 | } 355 | 356 | func TestMockEs(t *testing.T) { 357 | expectResponse := []*iface.KV{ 358 | { 359 | Id: 1, 360 | Key: 10, 361 | Val: 100, 362 | Name: "k1", 363 | }, 364 | { 365 | Id: 2, 366 | Key: 20, 367 | Val: 200, 368 | Name: "k2", 369 | }, 370 | { 371 | Id: 3, 372 | Key: 30, 373 | Val: 300, 374 | Name: "k3", 375 | }, 376 | } 377 | str := ` 378 | { 379 | "hits": { 380 | "hits": [ 381 | { 382 | "_source": { 383 | "id": 1, 384 | "key": 10, 385 | "val": 100, 386 | "name": "k1", 387 | "@version": "1", 388 | "@timestamp": "2021-12-12T22:39:55.760Z" 389 | } 390 | }, 391 | { 392 | "_source": { 393 | "id": 2, 394 | "key": 20, 395 | "val": 200, 396 | "name": "k2", 397 | "@version": "1", 398 | "@timestamp": "2021-12-13T22:39:55.760Z" 399 | } 400 | }, 401 | { 402 | "_source": { 403 | "id": 3, 404 | "key": 30, 405 | "val": 300, 406 | "name": "k3", 407 | "@version": "1", 408 | "@timestamp": "2021-12-14T22:39:55.760Z" 409 | } 410 | } 411 | ] 412 | } 413 | }` 414 | p := NewExpectParam(). 415 | WithMethod("POST"). 416 | WithPath("http://mockes.com/_all/_search"). 417 | WithReturns(str) 418 | kit.HttpExpect(p, t) 419 | list, err := srv.es.List(ctx, 1, 2, 3) 420 | assert.Nil(t, err) 421 | assert.Equal(t, expectResponse, list) 422 | } 423 | -------------------------------------------------------------------------------- /mockimpl/es.go: -------------------------------------------------------------------------------- 1 | package mockimpl 2 | 3 | import ( 4 | "context" 5 | "github.com/olivere/elastic/v7" 6 | "github.com/shihuo-cn/mockit/iface" 7 | "net/http" 8 | "reflect" 9 | "time" 10 | ) 11 | 12 | var _ iface.DemoInterface = &MockEs{} 13 | 14 | func NewMockEs(cli *http.Client) *MockEs { 15 | url := "http://mockes.com" 16 | client, err := elastic.NewSimpleClient(elastic.SetURL(url), elastic.SetHttpClient(cli)) 17 | if err != nil { 18 | panic(err) 19 | } 20 | return &MockEs{cli: client} 21 | } 22 | 23 | type MockEs struct { 24 | cli *elastic.Client 25 | } 26 | 27 | func (m MockEs) First(ctx context.Context, key int64) (int64, error) { 28 | panic("implement me") 29 | } 30 | 31 | func (m MockEs) Put(ctx context.Context, key, val int64) error { 32 | panic("implement me") 33 | } 34 | 35 | func (m MockEs) List(ctx context.Context, relationId int64, pageIndex, pageSize int) ([]*iface.KV, error) { 36 | termQuery := elastic.NewTermQuery("rid", relationId) 37 | res, err := m.cli.Search("_all"). 38 | Query(termQuery). 39 | Sort("@timestamp", false). 40 | Size(pageSize). 41 | Do(ctx) 42 | if err != nil { 43 | return nil, err 44 | } 45 | msgNum := len(res.Hits.Hits) 46 | if msgNum == 0 { 47 | return nil, nil 48 | } 49 | list := make([]*iface.KV, msgNum) 50 | for i, item := range res.Each(reflect.TypeOf(&iface.KV{})) { 51 | list[i] = item.(*iface.KV) 52 | } 53 | return list, nil 54 | } 55 | 56 | func (m MockEs) Aggregate(ctx context.Context, dm *iface.DemoInterfaceModel, time time.Time) ([]*iface.DemoInterfaceModel, int, error) { 57 | panic("implement me") 58 | } 59 | -------------------------------------------------------------------------------- /mockimpl/http.go: -------------------------------------------------------------------------------- 1 | package mockimpl 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/pkg/errors" 8 | "github.com/shihuo-cn/mockit/iface" 9 | "io/ioutil" 10 | "net/http" 11 | "strconv" 12 | "time" 13 | ) 14 | 15 | var _ iface.DemoInterface = &MockHttp{} 16 | 17 | func NewMockHttp(cli *http.Client) *MockHttp { 18 | m := &MockHttp{ 19 | cli: cli, 20 | url: "http://mockhttp.com", 21 | } 22 | return m 23 | } 24 | 25 | type MockHttp struct { 26 | cli *http.Client 27 | url string 28 | } 29 | 30 | func (m *MockHttp) First(ctx context.Context, key int64) (int64, error) { 31 | resp, err := m.cli.Get(m.url + "/first?" + strconv.FormatInt(key, 10)) 32 | if err != nil { 33 | return 0, err 34 | } 35 | if resp.StatusCode != http.StatusOK { 36 | return 0, errors.New(resp.Status) 37 | } 38 | buff, err := ioutil.ReadAll(resp.Body) 39 | if err != nil { 40 | return 0, err 41 | } 42 | num, err := strconv.ParseInt(string(buff), 10, 64) 43 | if err != nil { 44 | return 0, err 45 | } 46 | return num, nil 47 | } 48 | 49 | func (m *MockHttp) Put(ctx context.Context, key, val int64) error { 50 | panic("implement me") 51 | } 52 | 53 | func (m *MockHttp) List(ctx context.Context, relationId int64, pageIndex, pageSize int) ([]*iface.KV, error) { 54 | resp, err := m.cli.Get(fmt.Sprintf("%s/list?relationId=%d&pageIndex=%d&pageSize=%d", m.url, relationId, pageIndex, pageSize)) 55 | if err != nil { 56 | return nil, err 57 | } 58 | if resp.StatusCode != http.StatusOK { 59 | return nil, errors.New(resp.Status) 60 | } 61 | buff, err := ioutil.ReadAll(resp.Body) 62 | if err != nil { 63 | return nil, err 64 | } 65 | var arr []*iface.KV 66 | err = json.Unmarshal(buff, &arr) 67 | if err != nil { 68 | return nil, err 69 | } 70 | return arr, nil 71 | } 72 | 73 | func (m *MockHttp) Aggregate(ctx context.Context, dm *iface.DemoInterfaceModel, time time.Time) ([]*iface.DemoInterfaceModel, int, error) { 74 | panic("implement me") 75 | } 76 | -------------------------------------------------------------------------------- /mockimpl/interface.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: ./iface/interface.go 3 | 4 | // Package mockimpl is a generated GoMock package. 5 | package mockimpl 6 | 7 | import ( 8 | context "context" 9 | reflect "reflect" 10 | time "time" 11 | 12 | gomock "github.com/golang/mock/gomock" 13 | iface "github.com/shihuo-cn/mockit/iface" 14 | ) 15 | 16 | // MockDemoInterface is a mock of DemoInterface interface. 17 | type MockDemoInterface struct { 18 | ctrl *gomock.Controller 19 | recorder *MockDemoInterfaceMockRecorder 20 | } 21 | 22 | // MockDemoInterfaceMockRecorder is the mock recorder for MockDemoInterface. 23 | type MockDemoInterfaceMockRecorder struct { 24 | mock *MockDemoInterface 25 | } 26 | 27 | // NewMockDemoInterface creates a new mock instance. 28 | //func NewMockDemoInterface(ctrl *gomock.Controller) *MockDemoInterface 29 | func NewMockDemoInterface(ctrl *gomock.Controller) interface{} { 30 | mock := &MockDemoInterface{ctrl: ctrl} 31 | mock.recorder = &MockDemoInterfaceMockRecorder{mock} 32 | return mock 33 | } 34 | 35 | // EXPECT returns an object that allows the caller to indicate expected use. 36 | func (m *MockDemoInterface) EXPECT() *MockDemoInterfaceMockRecorder { 37 | return m.recorder 38 | } 39 | 40 | // Aggregate mocks base method. 41 | func (m *MockDemoInterface) Aggregate(ctx context.Context, dm *iface.DemoInterfaceModel, time time.Time) ([]*iface.DemoInterfaceModel, int, error) { 42 | m.ctrl.T.Helper() 43 | ret := m.ctrl.Call(m, "Aggregate", ctx, dm, time) 44 | ret0, _ := ret[0].([]*iface.DemoInterfaceModel) 45 | ret1, _ := ret[1].(int) 46 | ret2, _ := ret[2].(error) 47 | return ret0, ret1, ret2 48 | } 49 | 50 | // Aggregate indicates an expected call of Aggregate. 51 | func (mr *MockDemoInterfaceMockRecorder) Aggregate(ctx, dm, time interface{}) *gomock.Call { 52 | mr.mock.ctrl.T.Helper() 53 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Aggregate", reflect.TypeOf((*MockDemoInterface)(nil).Aggregate), ctx, dm, time) 54 | } 55 | 56 | // First mocks base method. 57 | func (m *MockDemoInterface) First(ctx context.Context, key int64) (int64, error) { 58 | m.ctrl.T.Helper() 59 | ret := m.ctrl.Call(m, "First", ctx, key) 60 | ret0, _ := ret[0].(int64) 61 | ret1, _ := ret[1].(error) 62 | return ret0, ret1 63 | } 64 | 65 | // First indicates an expected call of First. 66 | func (mr *MockDemoInterfaceMockRecorder) First(ctx, key interface{}) *gomock.Call { 67 | mr.mock.ctrl.T.Helper() 68 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "First", reflect.TypeOf((*MockDemoInterface)(nil).First), ctx, key) 69 | } 70 | 71 | // List mocks base method. 72 | func (m *MockDemoInterface) List(ctx context.Context, relationId int64, pageIndex, pageSize int) ([]*iface.KV, error) { 73 | m.ctrl.T.Helper() 74 | ret := m.ctrl.Call(m, "List", ctx, relationId, pageIndex, pageSize) 75 | ret0, _ := ret[0].([]*iface.KV) 76 | ret1, _ := ret[1].(error) 77 | return ret0, ret1 78 | } 79 | 80 | // List indicates an expected call of List. 81 | func (mr *MockDemoInterfaceMockRecorder) List(ctx, relationId, pageIndex, pageSize interface{}) *gomock.Call { 82 | mr.mock.ctrl.T.Helper() 83 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockDemoInterface)(nil).List), ctx, relationId, pageIndex, pageSize) 84 | } 85 | 86 | // Put mocks base method. 87 | func (m *MockDemoInterface) Put(ctx context.Context, key, val int64) error { 88 | m.ctrl.T.Helper() 89 | ret := m.ctrl.Call(m, "Put", ctx, key, val) 90 | ret0, _ := ret[0].(error) 91 | return ret0 92 | } 93 | 94 | // Put indicates an expected call of Put. 95 | func (mr *MockDemoInterfaceMockRecorder) Put(ctx, key, val interface{}) *gomock.Call { 96 | mr.mock.ctrl.T.Helper() 97 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockDemoInterface)(nil).Put), ctx, key, val) 98 | } 99 | -------------------------------------------------------------------------------- /mockimpl/mysql.go: -------------------------------------------------------------------------------- 1 | package mockimpl 2 | 3 | import ( 4 | "context" 5 | "github.com/shihuo-cn/mockit/iface" 6 | "gorm.io/gorm" 7 | "time" 8 | ) 9 | 10 | var _ iface.DemoInterface = &mockMySQL{} 11 | 12 | type mockMySQL struct { 13 | db *gorm.DB 14 | } 15 | 16 | func NewSqlDao(db *gorm.DB) iface.DemoInterface { 17 | return &mockMySQL{ 18 | db: db, 19 | } 20 | } 21 | func (s *mockMySQL) tableDemo(ctx context.Context) *gorm.DB { 22 | return s.db.Table("demo") 23 | } 24 | 25 | func (s *mockMySQL) First(ctx context.Context, key int64) (int64, error) { 26 | var count int64 27 | err := s.tableDemo(ctx). 28 | Where("key = ?", key). 29 | Select("COUNT(1)").Count(&count).Error 30 | if err != nil { 31 | return 0, err 32 | } 33 | return count, nil 34 | } 35 | 36 | func (s *mockMySQL) Put(ctx context.Context, key, val int64) error { 37 | m := &iface.KV{ 38 | Key: key, 39 | Val: val, 40 | Name: "name", 41 | } 42 | err := s.tableDemo(ctx).Create(m).Error 43 | if err != nil { 44 | return err 45 | } 46 | return nil 47 | } 48 | 49 | func (s *mockMySQL) List(ctx context.Context, relationId int64, pageIndex, pageSize int) ([]*iface.KV, error) { 50 | var arr []*iface.KV 51 | err := s.tableDemo(ctx). 52 | Where("relation_id = ? AND is_deleted = ?", 53 | relationId, false). 54 | Offset((pageIndex - 1) * pageSize). 55 | Limit(pageSize). 56 | Scan(&arr).Error 57 | if err != nil { 58 | return nil, err 59 | } 60 | return arr, nil 61 | } 62 | 63 | func (s *mockMySQL) Aggregate(ctx context.Context, m *iface.DemoInterfaceModel, time time.Time) ([]*iface.DemoInterfaceModel, int, error) { 64 | panic("implement me") 65 | } 66 | -------------------------------------------------------------------------------- /mockimpl/redis.go: -------------------------------------------------------------------------------- 1 | package mockimpl 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/go-redis/redis/v8" 7 | "github.com/shihuo-cn/mockit/iface" 8 | "time" 9 | ) 10 | 11 | var _ iface.DemoInterface = &MockRedis{} 12 | 13 | type MockRedis struct { 14 | cli *redis.Client 15 | kf keyFormatter 16 | } 17 | type keyFormatter struct { 18 | } 19 | 20 | func (keyFormatter) formatKey(key int64) string { 21 | return fmt.Sprintf("SHOHUO:%d", key) 22 | } 23 | func (m MockRedis) First(ctx context.Context, key int64) (int64, error) { 24 | k := m.kf.formatKey(key) 25 | num, err := m.cli.Get(ctx, k).Int64() 26 | if err != nil { 27 | return 0, err 28 | } 29 | return num, nil 30 | } 31 | 32 | func (m MockRedis) Put(ctx context.Context, key, val int64) error { 33 | k := m.kf.formatKey(key) 34 | err := m.cli.Set(ctx, k, val, 0).Err() 35 | return err 36 | } 37 | 38 | func (m MockRedis) List(ctx context.Context, relationId int64, pageIndex, pageSize int) ([]*iface.KV, error) { 39 | panic("implement me") 40 | } 41 | 42 | func (m MockRedis) Aggregate(ctx context.Context, dm *iface.DemoInterfaceModel, time time.Time) ([]*iface.DemoInterfaceModel, int, error) { 43 | panic("implement me") 44 | } 45 | 46 | func NewMockRedis(addr string) *MockRedis { 47 | rdb := redis.NewClient(&redis.Options{ 48 | Addr: addr, 49 | Password: "", 50 | DB: 0, 51 | }) 52 | return &MockRedis{ 53 | cli: rdb, 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /paramter.go: -------------------------------------------------------------------------------- 1 | package mockit 2 | 3 | import "net/http" 4 | 5 | // ExpectParam mockit ioc/控制反转 参数 6 | type ExpectParam interface { 7 | // WithPath 路径类 8 | // interface : mock client name 9 | // http: url "http://mock.com/get" 10 | WithPath(path string) ExpectParam 11 | // WithMethod 动作/方法类 12 | // http : POST GET DELETE等 13 | // mysql: "SELECT (.+) FROM DEMO" "INSERT INTO$"等 14 | WithMethod(method string) ExpectParam 15 | // WithArgs 入参 16 | WithArgs(args ...interface{}) ExpectParam 17 | // WithArgsByIndex 下标入参 18 | // NOTE: 目前针对interface封装,不覆盖的会走Any(忽略匹配)逻辑 19 | WithArgsByIndex(index int, value interface{}) ExpectParam 20 | // WithHttpResponseFunc 设置Http response 21 | // http 设置可变response用 22 | WithHttpResponseFunc(f func(req *http.Request) (*http.Response, error)) ExpectParam 23 | // WithReturns 设置预期返回值 24 | // http: 限制1个,默认匹配interface类型,匹配string时,response 返回为string,其他情况response使用json 25 | // mysql: 支持 struct/slice/array 默认使用json tag,如果mockit设置tag为其他,如"mock",可参见单元测试 26 | // 其他: 全量 27 | WithReturns(m ...interface{}) ExpectParam 28 | // WithKeyValReturn 设置单个预期返回值 29 | // mysql:如查询计数时,可使用 "COUNT", 20 => 代表返回20,注意这个`COUNT`必须和查询语句的大小写相同 30 | // http: 支持 string json 2种key,区分response返回的类型 31 | WithKeyValReturn(key ExpectKey, val interface{}) ExpectParam 32 | } 33 | 34 | // ExpectKey custom key 35 | // In the case of mysql: `json` `string` `func` choose excepted response 36 | type ExpectKey = string 37 | 38 | const ( 39 | // for http response encode 40 | HttpResponseFunc ExpectKey = "func" // first 41 | HttpResponseString ExpectKey = "string" // second 42 | HttpResponseJson ExpectKey = "json" // third 43 | 44 | ) 45 | 46 | type expectParam struct { 47 | // 动作/方法 48 | // http : POST GET DELETE等 49 | // mysql: "SELECT (.+) FROM DEMO" "INSERT INTO$"等 50 | method string 51 | // interface : mock client name 52 | // http: url "http://mock.com/get" 53 | path string 54 | 55 | // default: 56 | // 0: not set 57 | // 1: WithArgs 58 | // 2: WithArgsByIndex 59 | argsKind argsKind 60 | args []interface{} 61 | idxArgs map[int]interface{} 62 | 63 | // return 64 | returns []interface{} 65 | key ExpectKey 66 | val interface{} 67 | responseHandler func(req *http.Request) (*http.Response, error) 68 | } 69 | 70 | func NewExpectParam() ExpectParam { 71 | return new(expectParam) 72 | } 73 | func (p *expectParam) WithPath(path string) ExpectParam { 74 | p.path = path 75 | return p 76 | } 77 | func (p *expectParam) WithMethod(method string) ExpectParam { 78 | p.method = method 79 | return p 80 | } 81 | func (p *expectParam) WithArgs(args ...interface{}) ExpectParam { 82 | if !(p.argsKind == normal || p.argsKind == notSet) { 83 | return p 84 | } 85 | p.args = args 86 | p.argsKind = normal 87 | return p 88 | } 89 | 90 | func (p *expectParam) WithArgsByIndex(index int, value interface{}) ExpectParam { 91 | if !(p.argsKind == byIndex || p.argsKind == notSet) { 92 | return p 93 | } 94 | if len(p.idxArgs) == 0 { 95 | p.idxArgs = make(map[int]interface{}) 96 | } 97 | p.argsKind = byIndex 98 | p.idxArgs[index] = value 99 | return p 100 | } 101 | 102 | func (p *expectParam) WithHttpResponseFunc(f func(req *http.Request) (*http.Response, error)) ExpectParam { 103 | p.responseHandler = f 104 | p.key = HttpResponseFunc 105 | return p 106 | } 107 | func (p *expectParam) WithReturns(m ...interface{}) ExpectParam { 108 | if p == nil { 109 | p = new(expectParam) 110 | } 111 | p.returns = m 112 | return p 113 | } 114 | func (p *expectParam) WithKeyValReturn(key ExpectKey, val interface{}) ExpectParam { 115 | p.key = key 116 | p.val = val 117 | return p 118 | } 119 | func (p *expectParam) WithHttpResponseHandler(f func(req *http.Request) (*http.Response, error)) ExpectParam { 120 | p.key = HttpResponseFunc 121 | p.responseHandler = f 122 | return p 123 | } 124 | --------------------------------------------------------------------------------