Code Monkey home page Code Monkey logo

tf's Introduction

tf

tf is a microframework for parametrized testing of functions and HTTP in Go.

Functions

It offers a simple and intuitive syntax for tests by wrapping the function:

// Remainder returns the quotient and remainder from dividing two integers.
func Remainder(a, b int) (int, int) {
    return a / b, a % b
}

func TestRemainder(t *testing.T) {
    Remainder := tf.Function(t, Remainder)

    Remainder(10, 3).Returns(3, 1)
    Remainder(10, 2).Returns(5, 0)
    Remainder(17, 7).Returns(2, 3)
}

Assertions are performed with testify. If an assertion fails it will point to the correct line so you do not need to explicitly label tests.

The above test will output (in verbose mode):

=== RUN   TestRemainder
--- PASS: TestRemainder (0.00s)
=== RUN   TestRemainder/Remainder#1
--- PASS: TestRemainder/Remainder#1 (0.00s)
=== RUN   TestRemainder/Remainder#2
--- PASS: TestRemainder/Remainder#2 (0.00s)
=== RUN   TestRemainder/Remainder#3
--- PASS: TestRemainder/Remainder#3 (0.00s)
PASS

Grouping

Use NamedFunction to specify a custom name for the function/group:

func TestNamedSum(t *testing.T) {
	Sum := tf.NamedFunction(t, "Sum1", Item.Add)

	Sum(Item{1.3, 4.5}, 3.4).Returns(9.2)
	Sum(Item{1.3, 4.6}, 3.5).Returns(9.4)

	Sum = tf.NamedFunction(t, "Sum2", Item.Add)

	Sum(Item{1.3, 14.5}, 3.4).Returns(19.2)
	Sum(Item{21.3, 4.6}, 3.5).Returns(29.4)
}

Struct Functions

You can test struct functions by providing the struct value as the first parameter followed by any function arguments, if any.

type Item struct {
	a, b float64
}

func (i Item) Add(c float64) float64 {
	return i.a + i.b + c
}

func TestItem_Add(t *testing.T) {
	Sum := tf.Function(t, Item.Add)

	Sum(Item{1.3, 4.5}, 3.4).Returns(9.2)
}

HTTP Testing

Client

Super easy HTTP testing by using the ServeHTTP function. This means that you do not have to run the server and it is compatible with all HTTP libraries and frameworks but has all the functionality of the server itself.

The simplest example is to use the default muxer in the http package:

http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello, World!")
})

And now we can write some tests:

func TestHTTPRouter(t *testing.T) {
	run := tf.ServeHTTP(t, http.DefaultServeMux.ServeHTTP)

	run(&tf.HTTPTest{
		Path:         "/hello",
		Status:       http.StatusOK,
		ResponseBody: strings.NewReader("Hello, World!"),
	})

	run(&tf.HTTPTest{
		Path:   "/world",
		Status: http.StatusNotFound,
	})
}

It is compatible with all HTTP frameworks because they must all expose a ServeHTTP which is the entry point for the request router/handler.

There are many more options for HTTPTest. Some HTTP tests require multiple operations, you can use MultiHTTPTest for this:

run(&tf.MultiHTTPTest{
	Steps: []*tf.HTTPTest{
		{
			Path:        "/save",
			Method:      http.MethodPut,
			RequestBody: strings.NewReader(`{"foo":"bar"}`),
			Status:      http.StatusCreated,
		},
		{
			Path:         "/fetch",
			Method:       http.MethodGet,
			Status:       http.StatusOK,
			ResponseBody: strings.NewReader(`{"foo":"bar"}`),
		},
	},
})

Each step will only proceed if the previous step was successful.

Server

Sometimes you need to mock HTTP servers where the only option is to provide a URL endpoint through to your test. That is, when you do not have direct access to the router, or it's impractical to inject the behavior.

For case this you can use a HTTPServer:

// 0 means to use a random port, or you can provide your own.
server := tf.StartHTTPServer(0).
	AddHandlers(map[string]http.HandlerFunc{
		"/hi": func(w http.ResponseWriter, r *http.Request) {
			w.Write([]byte(`hello`))
		},
		"/easy": tf.HTTPJSONResponse(200, []int{1, 2, 3}),
	})

// Always remember to tear down the resources when the test ends.
defer server.Shutdown()

// Your test code here...
server.Endpoint() // http://localhost:61223

Using a real HTTP server has some benefits:

  1. It's isolated. That means it does not interfere in anyway with the global HTTP server.

  2. It can be used in parallel. You can either share the same HTTPServer across many tests (such as in TestMain), or create one for each test in parallel. Providing 0 for the port (as in the example above) will ensure that it always selects an unused random port.

  3. It mutable. After creating and starting the HTTPServer you can add/remove handlers. This is useful when most tests need a base logic, but some cases need to return special under specific scenarios.

You can create your own handlers, of course, but there are a few common ones that also ship with tf:

  • HTTPEmptyResponse(statusCode int)
  • HTTPStringResponse(statusCode int, body string)
  • HTTPJSONResponse(statusCode int, body interface{})

Environment Variables

SetEnv sets an environment variable and returns a reset function to ensure the environment is always returned to it's previous state:

resetEnv := tf.SetEnv(t, "HOME", "/somewhere/else")
defer resetEnv()

If you would like to set multiple environment variables, you can use SetEnvs in the same way:

resetEnv := tf.SetEnvs(t, map[string]string{
    "HOME":  "/somewhere/else",
    "DEBUG": "on",
})
defer resetEnv()

tf's People

Contributors

elliotchance avatar m1ome avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

tf's Issues

Remove the testify dependency

testify is great, but tf doesn't really need have it as a dependency. Instead just use the build in reflect.DeepEqual, as testify does.

Smarter casting of similar types

For example trying to pass in an int to an int64 results in a panic. It should handle any casting that the compiler would normally handle.

Add Panics()

This works the same way as Errors in #1:

Divide(5, 0).Panics() // pass
Divide(5, 0).Panics("divide-by-zero") // pass
Divide(5, 0).Panics("foo") // fail

Can also be used with methods

This works:

type Item struct {
    a, b float64
}

func (i Item) Sum() {
    return i.a + i.b
}

func TestItem_Average(t *testing.T) {
    Sum := tf.Function(t, Item.Sum)

    Sum(Item{4.2, 5.1}).Returns(9.3)
}

Should add this to the docs.

Untyped nils need not cause a panic

Untyped nils need to be cast, like:

MyFunction(nil).Returns("foo") // will not work
MyFunction((MyStruct*)(nil)).Returns("foo") // works

Internally it should determine and cast the correct type of nil so that the caller doesn't have to worry about this.

Add Errors()

Based on the fact many functions return result, error it can test for the existence of an error or even further with a message:

Divide(5, 0).Errors() // pass
Divide(5, 0).Errors("divide-by-zero") // pass
Divide(5, 0).Errors("foo") // fail

True() and False()?

Might be easier for testing booking results?

IsUUID("2030169a-1723-434e-81b9-c627c48c4d8f").True()
IsUUID("foo").False()

Using structs as test fixtures

I find that if i'm testing a function with several parameters or long inputs the code style becomes much less readable and I result to a loop like:

func TestStringSliceApply(t *testing.T) {
	StringSliceApply := tf.Function(t, luigi.StringSliceApply)

	for _, test := range []struct {
		items   []string
		fn      func(string) string
		returns []string
	}{
		{nil, nil, nil},
		{[]string{}, nil, []string{}},
		{[]string{"foo", "bar"}, nil, []string{"foo", "bar"}},
		{[]string{"FOO", " Bar"}, strings.ToLower, []string{"foo", " bar"}},
		{[]string{"Bar "}, strings.ToUpper, []string{"BAR "}},
	} {
		StringSliceApply(test.items, test.fn).Returns(test.returns)
	}
}

For:

func StringSliceApply(items []string, fn func(string) string) []string

It would be nice to use a struct that automatically maps the arguments, like:

func TestStringSliceApply(t *testing.T) {
	tf.AutoFunction(t, luigi.StringSliceApply, []struct {
		items   []string
		fn      func(string) string
		returns []string
	}{
		{nil, nil, nil},
		{[]string{}, nil, []string{}},
		{[]string{"foo", "bar"}, nil, []string{"foo", "bar"}},
		{[]string{"FOO", " Bar"}, strings.ToLower, []string{"foo", " bar"}},
		{[]string{"Bar "}, strings.ToUpper, []string{"BAR "}},
	})
}

I'm not sure if AutoFunction is a very good name.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.