Reputation: 71
I'm having some issues implementing interface substitution for the Go code. Previously the code was not written with unit tests in mind and I am going back and adding support for testing (Testify and Mockery) and writing all of the Go tests for my code base.
The function I am currently trying to test previously received a pointer struct to the QueryTableResult provided by the Influx package. I have swapped it with a local interface:
type QueryTableResult interface {
Next() bool
Record() FluxRecord
Err() error
}
My main issue is the Record() provided by the influx package QueryTableResult struct returns a specific FluxRecord struct. I have added a local interface for this struct also:
type FluxRecord interface {
Time() time.Time
Value() interface{}
}
And have changed the QueryTableResult interface Record() method to return this interface instead of the specific struct from the influx package. When I do this I can no longer execute my influx query functions as I am getting this error:
cannot use <variable_name> (variable of type *api.QueryTableResult) as QueryTableResult value in argument to parseInfluxData: *api.QueryTableResult does not implement QueryTableResult (wrong type for method Record) have Record() *query.FluxRecord want Record() FluxRecord
I want the FluxRecord to satisfy the *query.FluxRecord type or vice versa. I'm not sure how else to go about changing this.
I am trying to test this function:
func parseInfluxData(queryResult QueryTableResult) (RecordResult, error) {
var formattedRecordResult RecordResult
for queryResult.Next() {
record := queryResult.Record()
timestamp := record.Time().UTC()
value := record.Value()
// Format the timestamp as desired
formattedTime := timestamp.Format(time.RFC3339)
// Convert the value to an integer
formattedValue, err := strconv.Atoi(fmt.Sprintf("%v", value))
if err != nil {
return formattedRecordResult, err
}
formattedRecordResult.Time = formattedTime
formattedRecordResult.Value = formattedValue
}
if queryResult.Err() != nil {
fmt.Printf("Query error: %s\n", queryResult.Err().Error())
}
return formattedRecordResult, nil
}
This is my test:
func TestParseInfluxData(t *testing.T) {
// Create a new instance of the QueryTableResult mock
queryTableResultMock := mocks.NewQueryTableResult(t)
// Create a new instance of the FluxRecord mock
fluxRecordMock := mocks.NewFluxRecord(t)
// Set up the Record method of the QueryTableResult mock to return the FluxRecord mock
queryTableResultMock.On("Record").Return(fluxRecordMock)
// Define the expected behavior of the mock's methods
queryTableResultMock.On("Next").Return(true).Once()
// queryTableResultMock.On("Next").Return(false).Once()
// Define the expected behavior of FluxRecord's Time() method
expectedTime := time.Date(2023, 7, 15, 12, 34, 56, 0, time.UTC)
fluxRecordMock.On("Time").Return(expectedTime)
// Define the expected behavior of FluxRecord's Value() method
expectedValue := 42
fluxRecordMock.On("Value").Return(expectedValue)
// Call the function being tested
result, err := parseInfluxData(queryTableResultMock)
assert.Nil(t, err)
// Assertions based on the expected result and error
assert.Equal(t, expectedTime.Format(time.RFC3339), result.Time)
assert.Equal(t, expectedValue, result.Value)
// Assert that all expected methods of the mock have been called
queryTableResultMock.AssertExpectations(t)
fluxRecordMock.AssertExpectations(t)
}
The test fails with this error:
./parseInfluxData_test.go:34:33: cannot use queryTableResultMock (variable of type *mocks.QueryTableResult) as QueryTableResult value in argument to parseInfluxData: *mocks.QueryTableResult does not implement QueryTableResult (wrong type for method Record) have Record() *mocks.FluxRecord want Record() FluxRecord
Here are the mocks:
// Code generated by mockery v2.32.0. DO NOT EDIT.
package mocks
import (
time "time"
"fmt"
mock "github.com/stretchr/testify/mock"
)
// FluxRecord is an autogenerated mock type for the FluxRecord type
type FluxRecord struct {
mock.Mock
}
// Time provides a mock function with given fields:
func (_m *FluxRecord) Time() time.Time {
ret := _m.Called()
var r0 time.Time
if rf, ok := ret.Get(0).(func() time.Time); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(time.Time)
}
return r0
}
// Value provides a mock function with given fields:
func (_m *FluxRecord) Value() interface{} {
fmt.Println("Time() method called")
ret := _m.Called()
var r0 interface{}
if rf, ok := ret.Get(0).(func() interface{}); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(interface{})
}
}
return r0
}
// NewFluxRecord creates a new instance of FluxRecord. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewFluxRecord(t interface {
mock.TestingT
Cleanup(func())
}) *FluxRecord {
mock := &FluxRecord{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
// Code generated by mockery v2.32.0. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
)
// QueryTableResult is an autogenerated mock type for the QueryTableResult type
type QueryTableResult struct {
mock.Mock
}
// Err provides a mock function with given fields:
func (_m *QueryTableResult) Err() error {
ret := _m.Called()
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Next provides a mock function with given fields:
func (_m *QueryTableResult) Next() bool {
ret := _m.Called()
var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// Record provides a mock function with given fields:
func (_m *QueryTableResult) Record() *FluxRecord {
ret := _m.Called()
var r0 *FluxRecord
if rf, ok := ret.Get(0).(func() *FluxRecord); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*FluxRecord)
}
}
return r0
}
// NewQueryTableResult creates a new instance of QueryTableResult. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewQueryTableResult(t interface {
mock.TestingT
Cleanup(func())
}) *QueryTableResult {
mock := &QueryTableResult{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
How can I implement interface substitution to achieve testability within my code? Or is there another way I should be doing this instead?
Upvotes: 4
Views: 322
Reputation: 51
So I've tried to do it by myself here. What I discover is you may be facing some circular dependency problems.
So here is how I solve it
Folder structure:
|
|__querytable
|__querytable.go // Here is where resides your interfaces FluxRecord and Query table
|__mocks
|__FluxRecord.go // this is generated by mock
|__QueryTableResult.go // this is generated by mock
|__test
|__test_test.go // The actual folder that you function parseInfluxData resides
Organizing that way it works. You may explore other alternatives for structuring your files. I didn't change your code, but I updated the mocked-generated structs. I hope that works for you.
// Code generated by mockery v2.32.2. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
test "your_path_to_the_query_table_package/querytable"
)
// QueryTableResult is an autogenerated mock type for the QueryTableResult type
type QueryTableResult struct {
mock.Mock
}
// Err provides a mock function with given fields:
func (_m *QueryTableResult) Err() error {
ret := _m.Called()
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Next provides a mock function with given fields:
func (_m *QueryTableResult) Next() bool {
ret := _m.Called()
var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// Record provides a mock function with given fields:
func (_m *QueryTableResult) Record() test.FluxRecord {
ret := _m.Called()
var r0 test.FluxRecord
if rf, ok := ret.Get(0).(func() test.FluxRecord); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(test.FluxRecord)
}
}
return r0
}
// NewQueryTableResult creates a new instance of QueryTableResult. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewQueryTableResult(t interface {
mock.TestingT
Cleanup(func())
}) *QueryTableResult {
mock := &QueryTableResult{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
// Code generated by mockery v2.32.2. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
time "time"
)
// FluxRecord is an autogenerated mock type for the FluxRecord type
type FluxRecord struct {
mock.Mock
}
// Time provides a mock function with given fields:
func (_m *FluxRecord) Time() time.Time {
ret := _m.Called()
var r0 time.Time
if rf, ok := ret.Get(0).(func() time.Time); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(time.Time)
}
return r0
}
// Value provides a mock function with given fields:
func (_m *FluxRecord) Value() interface{} {
ret := _m.Called()
var r0 interface{}
if rf, ok := ret.Get(0).(func() interface{}); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(interface{})
}
}
return r0
}
// NewFluxRecord creates a new instance of FluxRecord. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewFluxRecord(t interface {
mock.TestingT
Cleanup(func())
}) *FluxRecord {
mock := &FluxRecord{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
Upvotes: 0