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
Closed

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

pmallela opened this issue Oct 4, 2018 · 6 comments

Comments

@pmallela
Copy link

@pmallela pmallela 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

@acln0 acln0 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

@pmallela pmallela 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

@pmallela pmallela 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

@mark-rushakoff mark-rushakoff 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

@mvdan mvdan 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

@pmallela pmallela 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
@golang golang locked and limited conversation to collaborators Oct 4, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
5 participants
You can’t perform that action at this time.