Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API for avoiding manual type assertions #11

Open
josephbuchma opened this issue Mar 4, 2024 · 1 comment
Open

API for avoiding manual type assertions #11

josephbuchma opened this issue Mar 4, 2024 · 1 comment

Comments

@josephbuchma
Copy link
Contributor

josephbuchma commented Mar 4, 2024

Is your feature request related to a problem? Please describe.
Type assertions are annoying.

Describe the solution you'd like
Make things more statically typed & easier to use. Also reduce number of places to change when you add/remove/change parameters.

Describe alternatives you've considered
I'm using this helper function which does type assertions for me. Would be nice to have something like that built into the library:

func toGoldenMasterFn(fn interface{}) func(args ...any) any {
	return func(args ...any) any {
		fnVal := reflect.ValueOf(fn)
		if fnVal.Kind() != reflect.Func {
			panic("wrapFunctionWithAnyArgs: provided interface is not a function")
		}

		fnType := fnVal.Type()
		if fnType.IsVariadic() {
			panic("wrapFunctionWithAnyArgs: variadic functions are not supported")
		}

		if len(args) != fnType.NumIn() {
			panic(fmt.Sprintf("wrapFunctionWithAnyArgs: incorrect number of arguments. Expected %d, got %d", fnType.NumIn(), len(args)))
		}

		in := make([]reflect.Value, len(args))
		for i, arg := range args {
			in[i] = reflect.ValueOf(arg)
		}

		results := fnVal.Call(in)
		if len(results) != 1 {
			panic("wrapFunctionWithAnyArgs: function does not return exactly one result")
		}

		return results[0].Interface()
	}
}

// Usage:
func TestGoldenMaster(t *testing.T) {
    // define the wrapper function
    f := toGoldenMasterFn(func(title, part string, span int) any {
        return Border(title, part, span)
    })
    
    // define the input values to combine
    titles := []any{"Example 1", "Example long enough", "Another thing"}
    parts := []any{"-", "=", "*", "#"}
    times := []any{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    // run the test
    golden.Master(t, f, golden.Combine(titles, parts, times))
}

Additional context
Another option, maybe even better one - have a set of generic functions for accepting N generic paramters, like:

golang.MasterOf2[T1, T2](t *testing.T, fn func(a T1, b T2) any, params1 []T1, params2 []T2)
golang.MasterOf3[T1, T2, T3](t *testing.T, fn func(a T1, b T2, c T3) any, params1 []T1, params2 []T2, params3 []T3)
// make a bunch more of these to cover all realistic use-cases...

This looks a bit goofy, but in practice it's not that bad - similar pattern is widely used in Elm, for example, and it's just fine https://package.elm-lang.org/packages/elm/json/latest/Json-Decode#map

With this approach, example above could look like:

// Usage:
func TestGoldenMaster(t *testing.T) {
    // define the wrapper function
    f := func(title, part string, span int) any {
        return Border(title, part, span)
    })
    
    // define the input values to combine
    titles := []any{"Example 1", "Example long enough", "Another thing"}
    parts := []any{"-", "=", "*", "#"}
    times := []any{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    // run the test
    golden.MasterOf3(t, f, titles, parts, times)
}

Which is a bit cleaner, and in addition to that you can't swap parameters of different types by accident.

@franiglesias
Copy link
Owner

I would love to explore these ideas.

My initial thought was to avoid the MasterOfx pattern (I don't like it, ;-)) TBH, I'm biased because I'm not doing a lot of combinatorial testing lately, so I'm not bothered by the type assertion thing. Anyway, I'm open to any improvements in the dev experience of the library.

(Fun fact: in the PHP version you can type hint the params of the wrapper function, so you don't need to worry about casting or assert them. I discover this by chance.).

The toGoldenMasterFn approach looks interesting for me, and I think it's more aligned with the original design. Very clever.

Anyway, I think that both approaches can be introduced in parallel and live together with the existing API. So, I would like to start playing with them.

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants