franela/goblin

Can't range over data set inside Describe block

twhit027 opened this issue · 2 comments

Running table-driven tests with proper descriptions seems buggy (if it's even supported). If you range over test data within a Describe block, it runs each test with the very last data set in the range, giving invalid test results.

// Example of how it has to be done, but requires less-than optimal scoping
func TestFoo(t *testing.T) {
	g := Goblin(t)

	tests := []struct {
		Description string
		Input       bool
		Output      bool
	}{
		{
			Description: `should fail`,
			Input:       false,
			Output:      false,
		},
		{
			Description: `should pass`,
			Input:       false,
			Output:      true,
		},
	}

	for _, t := range tests {
		g.Describe(`foo()`, func() {
			g.It(t.Description, func() {
				out := foo(t.Input)
				g.Assert(out).Equal(t.Output)
			})
		})
	}
}

The above example is how you're required to write table-driven tests, which makes test output ugly by outputting the describe block for every iteration, and also makes scoping of tests more difficult (in terms of After, Before, etc). In this case, the first test will fail & the second test will pass, as expected.

// Example of how describe blocks should work, but breaks range
func TestFoo(t *testing.T) {
	g := Goblin(t)

	g.Describe(`foo()`, func() {
		tests := []struct {
			Description string
			Input       bool
			Output      bool
		}{
			{
				Description: `should fail`,
				Input:       false,
				Output:      false,
			},
			{
				Description: `should pass`,
				Input:       false,
				Output:      true,
			},
		}

		for _, t := range tests {
			g.It(t.Description, func() {
				out := foo(t.Input)
				g.Assert(out).Equal(t.Output)
			})
		}
	})
}

The second case is how I would expect the nesting of Describe blocks to work, but in this case, the tests will not run properly. Both tests will pass, running both iterations of the test with the last item in the data set. This is not a valid test result, as we're expecting the first test to fail.

The foo function in this example just accepts a boolean and inverts it.

func foo(input bool) bool {
	return !input
}

Is there any reason why tests using data ranges within a Describe block should break?

@twhit027 your second example is the correct approach. The reason why it's not working for you is not because of Goblin, but the fact that Go reuses the same variable in loops, check https://github.com/golang/go/wiki/CommonMistakes for further info.

you need to change your range loop to something like

		for _, run := range tests {
			var t = run
			g.It(t.Description, func() {
				out := foo(t.Input)
				g.Assert(out).Equal(t.Output)
			})
		}

Well, TIL! Thanks for the knowledge!