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

proposal: testing: expose a constructor for *testing.T #28021

Closed
pmallela opened this issue Oct 4, 2018 · 6 comments

Comments

Projects
None yet
5 participants
@pmallela
Copy link

commented Oct 4, 2018

Can we expose a constructor that creates a minimal *testing.T? (..or even make T an interface)?

I have some helpers which I use for ease of testing. e.g.

func Assert(t *testing.T, isTrue bool, args ...interface{}) {
	if !isTrue {
		t.Fatal(args...)
	}
}

I was thinking about testing this Assert, by passing in a new *testing.T and checking if t.Failed() but my code panics because T is unable to be initialized properly.

func TestAssertFails(t *testing.T) {
	t.Parallel()
	innerT := &testing.T{}
	testing_utils.Assert(innerT, false, "omg")
	if !innerT.Failed() {
		t.Errorf("Assert did not fail the test")
	}
}
	panic: test executed panic(nil) or runtime.Goexit
	
	goroutine 5 [running]:
	testing.tRunner.func1(0xc42009c0f0)
		/usr/local/Cellar/go/1.10.1/libexec/src/testing/testing.go:742 +0x29d
	runtime.Goexit()
		/usr/local/Cellar/go/1.10.1/libexec/src/runtime/panic.go:377 +0x117
	testing.(*common).FailNow(0xc42009c2d0)
		/usr/local/Cellar/go/1.10.1/libexec/src/testing/testing.go:553 +0x39
	testing.(*common).Fatal(0xc42009c2d0, 0xc420033f88, 0x1, 0x1)
		/usr/local/Cellar/go/1.10.1/libexec/src/testing/testing.go:591 +0x6f

@gopherbot gopherbot added this to the Proposal milestone Oct 4, 2018

@gopherbot gopherbot added the Proposal label Oct 4, 2018

@acln0

This comment has been minimized.

Copy link
Contributor

commented Oct 4, 2018

Consider making the function take a testing.TB instead of a *testing.T, and passing a fake / mock into the test if you need it.

@pmallela

This comment has been minimized.

Copy link
Author

commented Oct 4, 2018

OH. thanks @acln0 that's what i was looking for, closing this issue.

@pmallela pmallela closed this Oct 4, 2018

@pmallela

This comment has been minimized.

Copy link
Author

commented Oct 4, 2018

wait, you cannot implement testing.TB, due to an unexported private() function ಠ_ಠ
so i am unable to create a mock T. Re-opening this issue.

@pmallela pmallela reopened this Oct 4, 2018

@mark-rushakoff

This comment has been minimized.

Copy link
Contributor

commented Oct 4, 2018

When I've needed to write a fake testing.TB, I've just gone ahead and included an embedded *testing.T.

type tb struct {
	name            string
	mu              sync.Mutex
	output          []string
	skipped, failed bool
	// The testing.TB interface has an unexported method "to prevent users implementing the
	// interface and so future additions to it will not violate Go 1 compatibility."
	//
	// This may cause problems across Go versions, but let's ignore them and
	// work around that restriction by embedding a T so we satisfy the unexported methods of the interface.
	*testing.T
}

var _ testing.TB = (*tb)(nil)

func (t *tb) Error(args ...interface{}) {
	t.Log(args...)
	t.Fail()
}
func (t *tb) Errorf(format string, args ...interface{}) {
	t.Logf(format, args...)
	t.Fail()
}
func (t *tb) Fail() {
	t.mu.Lock()
	t.failed = true
	t.mu.Unlock()
}
func (t *tb) FailNow() {
	t.Fail()
	runtime.Goexit()
}
func (t *tb) Failed() bool {
	t.mu.Lock()
	failed := t.failed
	t.mu.Unlock()
	return failed
}
func (t *tb) Fatal(args ...interface{}) {
	t.Log(args...)
	t.FailNow()
}
func (t *tb) Fatalf(format string, args ...interface{}) {
	t.Logf(format, args...)
	t.FailNow()
}
func (t *tb) Log(args ...interface{}) {
	t.mu.Lock()
	defer t.mu.Unlock()
	t.output = append(t.output, fmt.Sprintln(args...))
}
func (t *tb) Logf(format string, args ...interface{}) {
	t.mu.Lock()
	defer t.mu.Unlock()
	// Ensure message ends with newline.
	if len(format) > 0 && format[len(format)-1] != '\n' {
		format += "\n"
	}
	t.output = append(t.output, fmt.Sprintf(format, args...))
}
func (t *tb) Name() string {
	return t.name
}
func (t *tb) Skip(args ...interface{}) {
	t.Log(args...)
	t.SkipNow()
}
func (t *tb) SkipNow() {
	t.mu.Lock()
	t.skipped = true
	t.mu.Unlock()
	runtime.Goexit()
}
func (t *tb) Skipf(format string, args ...interface{}) {
	t.Logf(format, args...)
	t.SkipNow()
}
func (t *tb) Skipped() bool {
	t.mu.Lock()
	skipped := t.skipped
	t.mu.Unlock()
	return skipped
}
func (t *tb) Helper() {}
@mvdan

This comment has been minimized.

Copy link
Member

commented Oct 4, 2018

Another option is to have your function take a smaller interface than testing.TB. You could only require a couple of exported methods, for example.

@pmallela

This comment has been minimized.

Copy link
Author

commented Oct 4, 2018

yess both of these solutions are great, thanks @mvdan and @mark-rushakoff 🙏🏽🙏🏽

I will just create my own interface and *testing.T will implement it.

@pmallela pmallela closed this Oct 4, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.