testify icon indicating copy to clipboard operation
testify copied to clipboard

Mock.On panics when it expects a `func` parameter

Open okmkey45 opened this issue 3 years ago • 1 comments

Hello, I've been having issues mocking an interface that expects a func as a parameter. Not sure if this is a bug or maybe an opportunity to improve a little bit the docs (And if this is not the place for this, I'm really really sorry. I hope you can redirect me to the right place to ask these questions)

My interface:

type ExcelFileReader interface {
	ForEachRow(
		fileAbsolutePath string,
		sheetName string,
		closure func(row []string) (bool, error), // Here it's where the issue starts
	) error
}

My implementation:

type FileParser struct {
	reader file.ExcelFileReader
}

func (fp FileParser) GetDataPoints(
	path string,
	df DataFile,
) (data []model.DataPoint, err error) {
	if path == "" {
		return data, fmt.Errorf("path cannot be empty")
	}
	if df.IsZero() {
		return data, fmt.Errorf("data file cannot be empty")
	}

	fpath := filepath.Join(path, df.Filename())

	err = fp.reader.ForEachRow(fpath, df.SheetName(), func(row []string) (bool, error) {
		dp, dpErr := parseDataPoint(row)
		if dpErr != nil {
			return false, dpErr
		}

		data = append(data, dp)
		return true, nil
	})

	return data, err
}

My mock:

type MockExcelFileReader struct {
	Mock mock.Mock
}

func (m MockExcelFileReader) ForEachRow(
	fileAbsolutePath string,
	sheetName string,
	closure func(row []string) (bool, error),
) error {
	args := m.Mock.Called(
		fileAbsolutePath,
		sheetName,
		closure,
	)
	return args.Error(0)
}

And inside my test, I have a helper function to create an instance of the mock:

func getTestExcelFileReader(
	testPath string,
	testDataFile DataFile,
	err error,
) mocks.MockExcelFileReader {
	tr := mocks.MockExcelFileReader{}

	tr.Mock.On(
		"ForEachRow",
		fmt.Sprintf("%s/%s", testPath, testDataFile.Filename()),
		testDataFile.SheetName(),
		mock.AnythingOfType("func(row []string) (bool, error)"),
	).Return(err)

	return tr
}

And when I run the test, what I get is:

panic: 

mock: Unexpected Method Call
-----------------------------

ForEachRow(string,string,func([]string) (bool, error))
                0: "some path/some filename"
                1: "some sheet name"
                2: (func([]string) (bool, error))(0x57b640)

The closest call I have is: 

ForEachRow(string,string,mock.AnythingOfTypeArgument)
                0: "some path/some filename"
                1: "some sheet name"
                2: "func(row []string) (bool, error)"


Diff: 0: PASS:  (string=some path/some filename) == (string=some path/some filename)
        1: PASS:  (string=some sheet name) == (string=some sheet name)
        2: FAIL:  type func(row []string) (bool, error) != type  - (func([]string) (bool, error)=0x57b640) [recovered]

My guess is there's a problem when I'm using mock.AnythingOfType("func(row []string) (bool, error)") inside the Mock.On method. And of course, I can't pass an actual func since by doing this, I'll get an exception:

panic: cannot use Func in expectations. Use mock.AnythingOfType("func([]string) (bool, error)")

Please let me know if more info is needed. If there's a problem on how I'm using the mock, then I'd like understand and maybe contribute with a tiny PR adding an example to the docs. I think this could help a few folks. I did some research on line and I found people asks these questions but no clear answer is found

okmkey45 avatar Apr 21 '22 11:04 okmkey45

The error message you got suggests this is a known limitation and shows you what you need to do:

panic: cannot use Func in expectations. Use mock.AnythingOfType("func([]string) (bool, error)")

This limitation is unlikely to be addressed as functions cannot be compared in Go, even using reflection.

brackendawson avatar Apr 25 '22 11:04 brackendawson