From 035a12b0feca6453d6310efe5d926e8b6d48dadf Mon Sep 17 00:00:00 2001 From: Piotr Persona Date: Thu, 30 May 2024 13:24:22 +0200 Subject: [PATCH 1/2] feat: Conditionally enable/disable Unset call Consider the following example test scenario: The goal is to test http server API. The test may set up a local http server, database and mocks to 3rd party services. Since the tests are calling mocked server it may be useful to use mock `EXPECT()` calls. Test structure: 1. Setup local server, database and mocks 2. Mock 3rd party service calls 3. Send request to local server 4. Assert response 5. Verify side effects - execute database query As an enhancement, such tests can easily be called against deployed application, by simply replacing server and database URLs. The only catch are mocked calls, which won't be executed, since the request is sent to a remote server. In such case it may be possible to call `Unset()` method with `WithUnsetToggle` option, which value is set based on the current environment. For example: ```go func Env() mock.UnsetOption { if os.Getenv("local") { return mock.WithUnsetToggle(false) } return mock.WithUnsetToggle(true) } // In test ... func TestAPI(t *testing.T) { // setup server, database and mocks mock.Call3rdParty(ctx, argument).Return(nil).Unset(Env()) response := client.Post(request) // assertions and database query } ``` In the above example, if environment is "local" Unset will be disabled, meaning that all mocks will execute. If environment is other than "local" the Unset will be enabled, meaning that mocks will not be called. Signed-off-by: Piotr Persona --- mock/mock.go | 41 ++++++++++++++++++++++++++++++++++++++++- mock/mock_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/mock/mock.go b/mock/mock.go index d5eb1ef55..1e562cf59 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -208,10 +208,49 @@ func (c *Call) On(methodName string, arguments ...interface{}) *Call { return c.Parent.On(methodName, arguments...) } +// unsetConfiguration stores Unset method call configuration. +type unsetConfiguration struct { + enabled bool +} + +// UnsetOption will allow to configure Unset method invocation. +type UnsetOption func(*unsetConfiguration) + +// WithUnsetToggle will configure unset value. If enabled == false the Unset +// function will not be called. Default: true - the Unset will be called. +// This option may be used to conditionally enable or disable mock calls. +func WithUnsetToggle(enabled bool) UnsetOption { + return func(uc *unsetConfiguration) { + uc.enabled = enabled + } +} + +// WithUnsetEnabled is shorthand for WithUnsetToggle(true). +func WithUnsetEnabled() UnsetOption { + return func(uc *unsetConfiguration) { + uc.enabled = true + } +} + +// WithUnsetEnabled is shorthand for WithUnsetToggle(false). +func WithUnsetDisabled() UnsetOption { + return func(uc *unsetConfiguration) { + uc.enabled = false + } +} + // Unset removes a mock handler from being called. // // test.On("func", mock.Anything).Unset() -func (c *Call) Unset() *Call { +func (c *Call) Unset(options ...UnsetOption) *Call { + configuration := &unsetConfiguration{enabled: true} + for _, option := range options { + option(configuration) + } + if !configuration.enabled { + return c + } + var unlockOnce sync.Once for _, arg := range c.Arguments { diff --git a/mock/mock_test.go b/mock/mock_test.go index b80a8a75b..f90e379e2 100644 --- a/mock/mock_test.go +++ b/mock/mock_test.go @@ -666,6 +666,35 @@ func Test_Mock_Unset_WithFuncPanics(t *testing.T) { }) } +func Test_Mock_Unset_WithUnsetToggle_Option(t *testing.T) { + // make a test impl object + var mockedService = new(TestExampleImplementation) + mockedService.On("TheExampleMethod", 1). + Unset(WithUnsetToggle(true)) + assert.Len(t, mockedService.ExpectedCalls, 0) + + mockedService.On("TestExampleMethod", 1). + Unset(WithUnsetToggle(false)) + assert.Len(t, mockedService.ExpectedCalls, 1) +} + +func Test_Mock_Unset_WithUnsetEnabled_Option(t *testing.T) { + // make a test impl object + var mockedService = new(TestExampleImplementation) + mockedService.On("TheExampleMethod", 1). + Unset(WithUnsetEnabled()) + + assert.Len(t, mockedService.ExpectedCalls, 0) +} + +func Test_Mock_Unset_WithUnsetDisabled_Option(t *testing.T) { + // make a test impl object + var mockedService = new(TestExampleImplementation) + mockedService.On("TheExampleMethod", 1). + Unset(WithUnsetDisabled()) + assert.Len(t, mockedService.ExpectedCalls, 1) +} + func Test_Mock_Return(t *testing.T) { // make a test impl object From e7ba2e0d2d5fc4c84de09e35394c92b8fdde5053 Mon Sep 17 00:00:00 2001 From: Piotr Persona Date: Thu, 30 May 2024 17:42:18 +0200 Subject: [PATCH 2/2] feat: Implement Toggle alternative Signed-off-by: Piotr Persona --- mock/mock.go | 9 +++++++++ mock/mock_test.go | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/mock/mock.go b/mock/mock.go index 1e562cf59..c4bc245a4 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -291,6 +291,15 @@ func (c *Call) Unset(options ...UnsetOption) *Call { return c } +// Toggle will conditionally enable or disable mock call via flag. +// By default Call is toggled. +func (c *Call) Toggle(t bool) *Call { + if t { + return c + } + return c.Unset() +} + // NotBefore indicates that the mock should only be called after the referenced // calls have been called as expected. The referenced calls may be from the // same mock instance and/or other mock instances. diff --git a/mock/mock_test.go b/mock/mock_test.go index f90e379e2..a38d9b239 100644 --- a/mock/mock_test.go +++ b/mock/mock_test.go @@ -695,6 +695,15 @@ func Test_Mock_Unset_WithUnsetDisabled_Option(t *testing.T) { assert.Len(t, mockedService.ExpectedCalls, 1) } +func Test_Mock_Toggle(t *testing.T) { + var mockedService = new(TestExampleImplementation) + mockedService.On("TheExampleMethod", 1).Toggle(true) + assert.Len(t, mockedService.ExpectedCalls, 1) + + mockedService.On("TheExampleMethod", 1).Toggle(false) + assert.Len(t, mockedService.ExpectedCalls, 0) +} + func Test_Mock_Return(t *testing.T) { // make a test impl object