ginkgo
ginkgo copied to clipboard
Question: Shared or Reusable Context
Hey, thanks for the awesome test lib!
I was trying to write a context block that I can reuse.
Essentially I want something that I can use like:
CoolContext = Context("So cool!", func() {
BeforeEach(func(){
... party ...
})
AfterEach(func(){
... cleanup ...
... nurse hangover ...
})
})
In(CoolContext(
It("rocks", func(){
Ω(... always wears sunglasses at night ...)
})
))
Is there an existing pattern that would allow this?
Hi @rejoshed. You can just use higher order functions in Go (that is functions that return functions). For example:
func contextFuncGenerator(s string) func() {
return func() {
BeforeEach(func() { ... })
AfterEach(func() { ... })
It(s, func() { ... })
}
}
Context("one", contextFuncGenerator("alpha"))
Context("two", contextFuncGenerator("beta"))
One gotcha involves when functions are actually invoked: Describe/Context/When immediately invoke their callback function, while It/Spec callback functions are invoked later when the test is actually run. This causes problems in for
loops, for example:
for i := range []int{1,2,3} {
It(fmt.Sprintf("matches %d", i), func() {
Expect(answer()).To(Equal(i)) // because the It() is invoked after the loop finishes, 'i' is always 3
})
}
It's worth referring to the docs for more details. My advice would be to do this sparingly as it's easy to get wrong, and can be tricky to understand.
In the case you talk about above, you could just have another It()
in the same Context()
. I'm assuming you know that, and your actual use case is more complex.
+1. Here's a couple of additional ideas/patterns. There really isn't a "right" or "wrong" way to do any of this - just whatever makes sense to you and helps you manage complexity:
You can write CoolContext
as:
func CoolContext(name string, specs func()) {
Context(fmt.Sprintf("So cool - %s", name), Offset(1), func() {
BeforeEach(func(){
... party ...
})
AfterEach(func(){
... cleanup ...
... nurse hangover ...
})
specs()
}
}
and then your tests look like:
CoolContext("bob", func() {
It("rocks", func(){
Ω(... always wears sunglasses at night ...)
})
It("rocks again", func(){
Ω(... always wears sunglasses at night ...)
})
})
Though in this case of shared setup/teardown I tend to prefer pulling out functions that do the cleanup/teardown (using Ginko's DeferCleanup
):
func PrepForParty() {
... party ...
DeferCleanup( ...cleanup... )
DeferCleanup( ...nurst hangover... )
}
and now you can just:
Context("So cool bob!", func() {
BeforeEach(func(){
PrepForParty()
})
It("rocks", func(){
Ω(... always wears sunglasses at night ...)
})
It("rocks again", func(){
Ω(... always wears sunglasses at night ...)
})
})
Wow! Thanks for all the great answers. I'll give them a try sometime in the next day or two.
I did try the higher order function option, but I had troubles when running a single test using my IDE. I'm not sure exactly which command it runs to run only the single test, so I'll need to check into that.
Basically when I ran the outer context, it worked fine, but when I ran the single test, it lost (I forgot at this point) either the outer or inner context.
I'm running ginkgo V2 atm btw.
Thanks again, and I'll update once I've tried some more things.
The IDE could struggle in that situation. You could mark the test as focused (either with FIt
or the Focus
decorator) and try to run all the tests, allowing Ginkgo to work out which test to run.