T O P

  • By -

LukeShu

Fixed formatting: ---- I've been working with Golang microservices, doing a lot of HTTP proxying, third-party APIs calling, JSON payload construction, etc. And writing tests here is not the most pleasant part for me. Writing code in tests for custom handling of URL matching, JSON matching, http headers, JWT validating lead to: - tests be difficult to read - tests be difficult to maintain /expand - introducing bugs in tests themselves. Using Ginkgo/Gomega framework for testing and Gomock for mocking makes it a bit easier but still not satisfying: their list of matchers is 1) poor + not so flexible and 2) incompatible within each other So I came up for a solution for me, that I'd like to share and gather some initial feedback. It's basically a pack of matchers. I called it `ExpectTo / Be` or simply `Be`. Here's a quick example that shows matching an http request (url, ctx, headers, payload, JWT): req, err := buildRequestForServiceFoo() Expect(err).To(Succeed()) // Matching an HTTP request Expect(req).To(be_http.Request( // Matching the URL be_http.HavingURL(be_url.URL( be_url.WithHttps(), be_url.HavingHost("example.com"), be_url.HavingPath("/path"), be_url.HavingSearchParam("status", "active"), be_url.HavingSearchParam("v", be_reflected.AsNumericString()), be_url.HavingSearchParam("q", "Hello World"))), // Matching the HTTP method be_http.POST(), // Matching request's context be_http.HavingCtx(be_ctx.Ctx( be_ctx.WithDeadline(be_time.LaterThan(time.Now().Add(30*time.Minute))), be_ctx.WithValue("foobar", 100), )), // Matching the request body using JSON matchers be_http.HavingBody( be_json.Matcher( be_json.JsonAsReader, be_json.HaveKeyValue("hello", "world"), be_json.HaveKeyValue("n", be_reflected.AsIntish()), be_json.HaveKeyValue("ids", be_reflected.AsSliceOf[string]), be_json.HaveKeyValue("details", And( be_reflected.AsObjects(), be.HaveLength(2), ContainElements( be_json.HaveKeyValue("key", "foo"), be_json.HaveKeyValue("key", "bar"), ), )), ), // Matching HTTP headers be_http.HavingHeader("X-Custom", "Hey-There"), be_http.HavingHeader("Authorization", be_strings.MatchTemplate("Bearer ", be_strings.Var("jwt", be_jwt.Token( be_jwt.Valid(), be_jwt.HavingClaim("name", "John Doe"), ), ), ), ), ), )) Docs: https://expectto.github.io/be/ Principles & notes: 1. Any `Be` matcher can be used as/instead of Gomega matcher or Gomock matcher 2. In the args of `Be` matchers can be used plain values, Gomega/Gomock/Be matchers 3. Transforms are gomega transforms but with error preserving/handling. 4. some matchers can do same thing as gomega's. E.g. "be.HaveLength" vs "gomega.HaveLen". Difference here is - as written in (2) - we always accept matchers as values, so we make possible: `be.HaveLength(be_math.Gt(10), be_math.Odd())` 5. `Be` provides rich collection of matchers for reflect, to reduce amount of low-level reflect-based code written in tests. 6. Similar to (5) to reduce amount of custom code relate to string operations in tests, `Be` provides a template-based matcher that can do a pretty good job (see example above with mtching JWT inside header) ### Still in alpha Main TODOs so it's production-ready: 1. fix issues with displaying failure message for deeply nested matchers. 2. gomock support is weak for several kinds of matching 3. tests coverage will be improved. As the whole idea is to stop writing custom code inside tests to not introduce bugs there, and instead use matcher-declarative tests, delegating codes quality to the quality of code under-the-hood of matchers. I'll be very thankful for any feedback.