onsi / ginkgo

A Modern Testing Framework for Go
http://onsi.github.io/ginkgo/
MIT License
8.29k stars 654 forks source link

[Question] Can functions be used to execute tests under certain conditions #1287

Closed Mo0rBy closed 11 months ago

Mo0rBy commented 11 months ago

I'm a bit confused as to why this is not working and I imagine something in my understanding of Go or Ginkgo is not right.

Here is my situation. I have a var _ = Describe() parent block with 3 Describe() children blocks inside. Within each of these 2 children Describe() blocks I have a test and then a call to a function that I want to use to run another test:

        Describe("when only the 'env' parameter is provided,", Ordered, ContinueOnFailure, func() {
            var role rbacv1.Role // Object I am setting and performing assertions on
            expectedRoleName := fmt.Sprintf("k8s-cr-test-env--%s", expectedRoleBindings[index])
            templateFile := fmt.Sprintf("templates/%s.yaml", expectedRole)

            BeforeAll(func() {
                options := &helm.Options{
                    ValuesFiles: []string{"values/envOnly.yaml"},
                }

                renderedOutput := helm.RenderTemplate(GinkgoT(), options, helmChartPath, "rbac", []string{templateFile})
                helm.UnmarshalK8SYaml(GinkgoT(), renderedOutput, &role) // Sets the "var role rbacv1.Role" object
            })

            Context(fmt.Sprintf("the '%s' Role template,", expectedRole), func() {
                It("should the have the correct metadata name", func() {
                                          // Perform assertions on the role object
                    Expect(role.ObjectMeta.Name).To(Equal(expectedRoleName))
                })

                                  // Passing the role object into the function call
                apiGroupTest(expectedRole, role)
            })
        })

And this is the function it is calling.

func apiGroupTest(expectedRole string, actualRole rbacv1.Role) {
    readHelmRole := "read-helm-role"
    readAuthorizationpolicyRole := "read-authorization-policy-role"
    readRequestAuthenticationRole := "read-request-authentication-role"

    if ((expectedRole == readHelmRole) || (expectedRole == readAuthorizationpolicyRole) || (expectedRole == readRequestAuthenticationRole)) {
        It("apiGroup value should be '*'", Label("apiGroup"), func() {
                         // Perform assertions on the role object passed to the function
            Expect(actualRole.Rules[0].APIGroups[0]).To(Equal("*"))
        })
    }
}

My problem is that it seems that the actualRole parameter being passed into the function just has all of it's fields set to nil but when the if block and the assertions are kept as part of the child Describe() block and NOT called as a function, it looks fine and works as I'd expect. So my question is, "is it possible to call functions like this to use parameterised It() nodes? Or any other node?"

onsi commented 11 months ago

hey @Mo0rBy - yes what you're wanting to do is generally possible and a fairly common pattern. The issue you're running into here is that Go passes structs in by value, not by reference. So when you call apiGroupTest(expectedRole, role), go is making a copy of role and passing it in at the time the function is invoked - which is during Ginkgo's Tree Construction Phase before the tests actually run and role is actually instantiated.

You can fix this by passing in a reference to role:

        Describe("when only the 'env' parameter is provided,", Ordered, ContinueOnFailure, func() {
            var role *rbacv1.Role //THIS IS NOW A POINTER
            expectedRoleName := fmt.Sprintf("k8s-cr-test-env--%s", expectedRoleBindings[index])
            templateFile := fmt.Sprintf("templates/%s.yaml", expectedRole)

            BeforeAll(func() {
                options := &helm.Options{
                    ValuesFiles: []string{"values/envOnly.yaml"},
                }

                renderedOutput := helm.RenderTemplate(GinkgoT(), options, helmChartPath, "rbac", []string{templateFile})
                helm.UnmarshalK8SYaml(GinkgoT(), renderedOutput, role) //JUST PASS IN THE POINTER HERE...
            })

            Context(fmt.Sprintf("the '%s' Role template,", expectedRole), func() {
                It("should the have the correct metadata name", func() {
                                          // Perform assertions on the role object
                    Expect(role.ObjectMeta.Name).To(Equal(expectedRoleName))
                })

                                  // Passing the role object into the function call
                apiGroupTest(expectedRole, role) //...AND HERE
            })
        })

...

//THIS NOW TAKES A POINTER
func apiGroupTest(expectedRole string, actualRole *rbacv1.Role) {
    readHelmRole := "read-helm-role"
    readAuthorizationpolicyRole := "read-authorization-policy-role"
    readRequestAuthenticationRole := "read-request-authentication-role"

    if ((expectedRole == readHelmRole) || (expectedRole == readAuthorizationpolicyRole) || (expectedRole == readRequestAuthenticationRole)) {
        It("apiGroup value should be '*'", Label("apiGroup"), func() {
                         // Perform assertions on the role object passed to the function
            Expect(actualRole.Rules[0].APIGroups[0]).To(Equal("*"))
        })
    }
}
Mo0rBy commented 11 months ago

Works perfectly, thanks @onsi. Would you prefer these types of questions be asked elsewhere? I've asked a few now and while they might be useful for others to see, I feel like the issues section for Ginkgo is not the correct place.

onsi commented 11 months ago

hey @Mo0rBy - thanks for checking, opening issues works for me and does allow for others to be able to search out these conversations too.