Skip to content

Commit

Permalink
chore: unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
johnabass committed Dec 26, 2024
1 parent 2ad3e7a commit 2a266ae
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 4 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ go 1.23

require (
github.com/spaolacci/murmur3 v1.1.0
github.com/stretchr/testify v1.9.0
github.com/stretchr/testify v1.10.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
2 changes: 1 addition & 1 deletion locator.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
var (
// ErrNoServices is returned by a Locator to indicate that the Locator contains
// no service entries.
ErrNoServices = errors.New("no services defined in this locator")
ErrNoServices = errors.New("no services defined")
)

// Locator is a service locator based on hashing input objects.
Expand Down
224 changes: 224 additions & 0 deletions locator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package medley

import (
"errors"
"testing"

"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
)

type LocatorSuite struct {
suite.Suite

object []byte
objectString string
}

func (suite *LocatorSuite) find(ml *MultiLocator[string]) ([]string, error) {
return ml.Find(suite.object)
}

func (suite *LocatorSuite) findString(ml *MultiLocator[string]) ([]string, error) {
return ml.FindString(suite.objectString)
}

func (suite *LocatorSuite) SetupTest() {
suite.objectString = "test value"
suite.object = []byte(suite.objectString)
}

func (suite *LocatorSuite) assertExpectations(testObjects ...any) bool {
return mock.AssertExpectationsForObjects(
suite.T(),
testObjects...,
)
}

func (suite *LocatorSuite) TestFindString() {
l := new(MockLocator[string])
l.ExpectFindSuccess(suite.object, "service1").Once()

actual, err := FindString(l, suite.objectString)
suite.NoError(err)
suite.Equal("service1", actual)

suite.assertExpectations(l)
}

func (suite *LocatorSuite) testMultiLocatorFindEmpty() {
ml := new(MultiLocator[string])
results, err := ml.Find(suite.object)
suite.ErrorIs(err, ErrNoServices)
suite.Empty(results)
}

func (suite *LocatorSuite) testMultiLocatorFindStringEmpty() {
ml := new(MultiLocator[string])
results, err := ml.FindString(suite.objectString)
suite.ErrorIs(err, ErrNoServices)
suite.Empty(results)
}

// testMultiLocatorAllSuccess sets the expectation for a suite.object call on contained
// locators and lets a test pass in a closure to invoke a method on the MultiLocator under test.
func (suite *LocatorSuite) testMultiLocatorAllSuccess(finder func(*MultiLocator[string]) ([]string, error)) func() {
return func() {
var (
l1 = new(MockLocator[string])
l2 = new(MockLocator[string])
l3 = new(MockLocator[string])

ml = NewMultiLocator(l1, l2)
)

l1.ExpectFindSuccess(suite.object, "service1").Times(2)
l2.ExpectFindSuccess(suite.object, "service2").Times(3)
l3.ExpectFindSuccess(suite.object, "service3").Times(2)

results, err := finder(ml)
suite.NoError(err)
suite.ElementsMatch([]string{"service1", "service2"}, results)

ml.Add(l3)
results, err = finder(ml)
suite.NoError(err)
suite.ElementsMatch([]string{"service1", "service2", "service3"}, results)

ml.Remove(l1)
results, err = finder(ml)
suite.NoError(err)
suite.ElementsMatch([]string{"service2", "service3"}, results)

suite.assertExpectations(l1, l2, l3)
}
}

// testMultiLocatorSomeMissingServices tests that FindXXX works correctly when some locators
// are missing services, but others aren't.
func (suite *LocatorSuite) testMultiLocatorSomeMissingServices(finder func(*MultiLocator[string]) ([]string, error)) func() {
return func() {
var (
l1 = new(MockLocator[string])
l2 = new(MockLocator[string])
l3 = new(MockLocator[string])

ml = NewMultiLocator(l1, l2, l3)
)

l1.ExpectFindSuccess(suite.object, "service1").Once()
l2.ExpectFindNoServices(suite.object).Once()
l3.ExpectFindSuccess(suite.object, "service3").Once()

results, err := finder(ml)
suite.NoError(err)
suite.ElementsMatch([]string{"service1", "service3"}, results)

suite.assertExpectations(l1, l2, l3)
}
}

// testMultiLocatorFail tests that FindXXX works correctly when a locator returns an error.
func (suite *LocatorSuite) testMultiLocatorFail(finder func(*MultiLocator[string]) ([]string, error)) func() {
return func() {
var (
expectedErr = errors.New("expected")

l1 = new(MockLocator[string])
l2 = new(MockLocator[string])
l3 = new(MockLocator[string])

ml = NewMultiLocator(l1, l2, l3)
)

l1.ExpectFindSuccess(suite.object, "service1").Once()
l2.ExpectFindFail(suite.object, expectedErr).Once()

results, err := finder(ml)
suite.ErrorIs(err, expectedErr)
suite.Empty(results)

suite.assertExpectations(l1, l2, l3)
}
}

func (suite *LocatorSuite) TestMultiLocator() {
suite.Run("Find", func() {
suite.Run("Empty", suite.testMultiLocatorFindEmpty)
suite.Run("AllSuccess", suite.testMultiLocatorAllSuccess(suite.find))
suite.Run("SomeMissingServices", suite.testMultiLocatorSomeMissingServices(suite.find))
suite.Run("Fail", suite.testMultiLocatorFail(suite.find))
})

suite.Run("FindString", func() {
suite.Run("Empty", suite.testMultiLocatorFindStringEmpty)
suite.Run("AllSuccess", suite.testMultiLocatorAllSuccess(suite.findString))
suite.Run("SomeMissingServices", suite.testMultiLocatorSomeMissingServices(suite.findString))
suite.Run("Fail", suite.testMultiLocatorFail(suite.findString))
})
}

func (suite *LocatorSuite) TestUpdatableLocator() {
var (
expectedErr = errors.New("expected error")

l1 = new(MockLocator[string])
l2 = new(MockLocator[string])
l3 = new(MockLocator[string])

ul = NewUpdatableLocator(l1)
)

suite.Require().NotNil(ul)

l1.ExpectFindSuccess(suite.object, "service1").Once()
l2.ExpectFindSuccess(suite.object, "service2").Once()
l3.ExpectFindFail(suite.object, expectedErr).Once()

result, err := ul.Find(suite.object)
suite.NoError(err)
suite.Equal("service1", result)

ul.Set(nil)
result, err = ul.Find(suite.object)
suite.ErrorIs(err, ErrNoServices)
suite.Empty(result)

ul.Set(l2)
result, err = ul.Find(suite.object)
suite.NoError(err)
suite.Equal("service2", result)

ul.Set(l3)
result, err = ul.Find(suite.object)
suite.ErrorIs(err, expectedErr)
suite.Empty(result)

suite.assertExpectations(l1, l2, l3)
}

func (suite *LocatorSuite) TestSetLocator() {
var (
l1 = new(MockLocator[string])
l2 = new(MockLocator[string])
ul = NewUpdatableLocator[string](nil)
)

l2.ExpectFindSuccess(suite.object, "service2").Once()
suite.Require().NotNil(ul)

suite.NotPanics(func() {
SetLocator(l1, l2) // nop, since l1 doesn't have a Set method
})

SetLocator(ul, l2)
result, err := ul.Find(suite.object)
suite.NoError(err)
suite.Equal("service2", result)

suite.assertExpectations(l1, l2)
}

func TestLocator(t *testing.T) {
suite.Run(t, new(LocatorSuite))
}
27 changes: 27 additions & 0 deletions mocks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package medley

import "github.com/stretchr/testify/mock"

type MockLocator[S Service] struct {
mock.Mock
}

func (m *MockLocator[S]) Find(object []byte) (S, error) {
args := m.Called(object)

svc, _ := args.Get(0).(S)
return svc, args.Error(1)
}

func (m *MockLocator[S]) ExpectFindSuccess(object any, result S) *mock.Call {
return m.On("Find", object).Return(result, error(nil))
}

func (m *MockLocator[S]) ExpectFindFail(object any, err error) *mock.Call {
var zero S
return m.On("Find", object).Return(zero, err)
}

func (m *MockLocator[S]) ExpectFindNoServices(object any) *mock.Call {
return m.ExpectFindFail(object, ErrNoServices)
}

0 comments on commit 2a266ae

Please sign in to comment.