franiglesias / golden

Golang Library for snapshot testing
MIT License
3 stars 2 forks source link

API for avoiding manual type assertions #11

Open josephbuchma opened 7 months ago

josephbuchma commented 7 months ago

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 commented 7 months ago

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!