ginkgo
ginkgo copied to clipboard
Request: Higher level `It` statements should be run for each child Context.
So in order to add a lot of different contexts in which one result should always be consistent, I currently have a lot of statements which say the same thing, often comparing two data structures which are used a lot. To simplify this, I propose that higher level It statements should be inherited by sub contexts within the same context. This will reduce the redundancy of written tests dramatically, as I won't have to copy and paste the same It block 8 times for one unit test file.
As an example, the following Go file:
package pkg
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestPlayground(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Test Playground")
}
var _ = Describe("Test Playground", func() {
Context("Functionality1", func() {
var (
someInt int
)
Context("Environment one", function1(someInt))
Context("Environment two", function2(someInt))
Context("Environment three", function3(someInt))
It("should do something for each context", func() {
Expect(someInt).To(Equal(4321))
})
})
})
func function1(number int) func() {
return func() {
BeforeEach(func() {
number = 4321
})
}
}
func function2(number int) func() {
return func() {
BeforeEach(func() {
number = 4321
})
}
}
func function3(number int) func() {
return func() {
BeforeEach(func() {
number = 4322
})
}
}
Should be functionally equal to:
package pkg
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestPlayground(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Test Playground")
}
var _ = Describe("Test Playground", func() {
Context("Functionality1", func() {
var (
someInt int
)
Context("Environment one", function1(someInt))
Context("Environment two", function2(someInt))
Context("Environment three", function3(someInt))
})
})
func function1(number int) func() {
return func() {
BeforeEach(func() {
number = 4321
})
It("should do something for each context", func() {
Expect(number).To(Equal(4321))
})
}
}
func function2(number int) func() {
return func() {
BeforeEach(func() {
number = 4321
})
It("should do something for each context", func() {
Expect(number).To(Equal(4321))
})
}
}
func function3(number int) func() {
return func() {
BeforeEach(func() {
number = 4322
})
It("should do something for each context", func() {
Expect(number).To(Equal(4321))
})
}
}
Could you share a bit more about your use case or point me at some actual code? The example you have in the issue is a bit confusing (specifically: the first two environments set number to 4321 while the third sets it to 4322, which will fail, - so I'm struggling to understand the underlying problem you are trying to solve).
Two quick suggestions based on what you've shared so far:
-
On the broad theme of shared testing patterns I'd suggest perusing the recommendations in the docs here.
-
For parametrized testing (which seems closest to what you are trying to do in your example) check out the Table-Driven Tests extension
I'm actually wondering if this could be re-opened. It seems like a good way to write invariant conditions into hierarchical tests. Though it probably shouldn't be lumped in with the regular It
function for compatibility reasons among others. For example:
Context("When BuildFoo is called on Bar", func() {
JustBeforeEach(func() {
out, err = BuildFoo(myBar);
})
Whenever("An error occurs", func() bool {
return err != nil
}).ItAlways("Returns an empty output", func() {
Expect(out).ToBe(Empty())
})
When("Bar is malformed", func() {
BeforeEach(func() {
myBar.Malformed = true
})
ItAlways("returns an error", func() {
Expect(err).To(HaveOccurred())
})
It(PreservesInvariants()) // leads to all registered Whenever...ItAlways blocks running within this node
When("the reason is \"too large\", func() {
BeforeEach(func() {
myBar.Reason = "too large"
})
It("contains too big in the error", func() {
Expect(err).ToMatch("too big")
})
})
When("the reason is \"too small\", func() {
BeforeEach(func() {
myBar.Reason = "too small"
})
It("contains too little in the error", func() {
Expect(err).ToMatch("too little")
})
})
})
When("Bar is good", func() {
BeforeEach(func() {
myBar.Good = true
})
ItAlways("returns no error", func() {
Expect(err).NotTo(HaveOccurred())
})
When("The color is red", func() {
BeforeEach(func() {
bar.Color = "red"
})
It("Returns fire", func() {
Expect(out.Element).To(Equal("fire"));
})
})
When("The color is yellow", func() {
BeforeEach(func() {
bar.Color = "yellow"
})
It("Returns gold", func() {
Expect(out.Element).To(Equal("gold"));
})
})
})
}
This is equivalent to
Context("When BuildFoo is called on Bar", func() {
JustBeforeEach(func() {
out, err = BuildFoo(myBar);
})
When("Bar is malformed", func() {
BeforeEach(func() {
myBar.Malformed = true
})
It("Returns an empty output", func() {
Expect(out).ToBe(Empty())
})
It("returns an error", func() {
Expect(err).To(HaveOccurred())
})
When("the reason is \"too large\", func() {
BeforeEach(func() {
myBar.Reason = "too large"
})
It("Returns an empty output", func() {
Expect(out).ToBe(Empty())
})
It("returns an error", func() {
Expect(err).To(HaveOccurred())
})
It("contains too big in the error", func() {
Expect(err).ToMatch("too big")
})
})
When("the reason is \"too small\", func() {
BeforeEach(func() {
myBar.Reason = "too small"
})
It("Returns an empty output", func() {
Expect(out).ToBe(Empty())
})
It("returns an error", func() {
Expect(err).To(HaveOccurred())
})
It("contains too little in the error", func() {
Expect(err).ToMatch("too little")
})
})
})
When("Bar is good", func() {
BeforeEach(func() {
myBar.Good = true
})
When("The color is red", func() {
BeforeEach(func() {
bar.Color = "red"
})
It("returns no error", func() {
Expect(err).NotTo(HaveOccurred())
})
It("Returns fire", func() {
Expect(out.Element).To(Equal("fire"));
})
})
When("The color is yellow", func() {
BeforeEach(func() {
bar.Color = "yellow"
})
It("returns no error", func() {
Expect(err).NotTo(HaveOccurred())
})
It("Returns gold", func() {
Expect(out.Element).To(Equal("gold"));
})
})
})
}
Having invariants "out of focus" like this is good because it helps isolate the checks that are unique to each subtree. DescribeTable
also works, but it tends to intertwine test setup, execution, and assertions.