title: Scheme is Nil when testing an operator scaffolded with operator-sdk
body: <p>I'm working on an early iteration of an operator, which I've scaffolded using operator-sdk. I've tried my best to follow the examples from the <a href="https://sdk.operatorframework.io/docs/building-operators/golang/tutorial/#implement-the-controller" rel="nofollow noreferrer">Operator SDK Golang Tutorial</a> and the <a href="https://book.kubebuilder.io/cronjob-tutorial/writing-tests.html" rel="nofollow noreferrer">Kubebuilder book</a>. I've found that I can deploy and run my operator to a local cluster, but I'm unable to run the test suite. My tests always produce a <code>panic: runtime error: invalid memory address or nil pointer dereference</code>, which I've tracked down to the fact that the <code>Scheme</code> is always Nil. But so far, I haven't been able to figure out why that is.</p>
  <p>In theory, I could skip the tests and just test out the operator in my local cluster, but that's going to be really brittle long-term. I'd like to be able to do TDD, and more importantly I'd like to have a test suite to go along with the operator to help maintain quality once it's in maintenance mode.</p>
  <p>Here's my <code>suite_test.go</code>, which I'm modified as little as possible from the scaffolded version (the changes I have made are from the <a href="https://book.kubebuilder.io/cronjob-tutorial/writing-tests.html" rel="nofollow noreferrer">Kubebuilder Book</a>):</p>
  <pre><code>package controllers
  
  import (
      &quot;path/filepath&quot;
      &quot;testing&quot;
  
      . &quot;github.com/onsi/ginkgo&quot;
      . &quot;github.com/onsi/gomega&quot;
      &quot;k8s.io/client-go/kubernetes/scheme&quot;
      &quot;k8s.io/client-go/rest&quot;
      ctrl &quot;sigs.k8s.io/controller-runtime&quot;
      &quot;sigs.k8s.io/controller-runtime/pkg/client&quot;
      &quot;sigs.k8s.io/controller-runtime/pkg/envtest&quot;
      &quot;sigs.k8s.io/controller-runtime/pkg/envtest/printer&quot;
      logf &quot;sigs.k8s.io/controller-runtime/pkg/log&quot;
      &quot;sigs.k8s.io/controller-runtime/pkg/log/zap&quot;
  
      mybatch &quot;mycorp.com/mybatch-operator/api/v1alpha1&quot;
      // +kubebuilder:scaffold:imports
  )
  
  // These tests use Ginkgo (BDD-style Go testing framework). Refer to
  // http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
  
  var cfg *rest.Config
  var k8sClient client.Client
  var testEnv *envtest.Environment
  
  func TestAPIs(t *testing.T) {
      RegisterFailHandler(Fail)
  
      RunSpecsWithDefaultAndCustomReporters(t,
          &quot;Controller Suite&quot;,
          []Reporter{printer.NewlineReporter{}})
  }
  
  var _ = BeforeSuite(func(done Done) {
      logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
  
      By(&quot;bootstrapping test environment&quot;)
      testEnv = &amp;envtest.Environment{
          CRDDirectoryPaths: []string{filepath.Join(&quot;..&quot;, &quot;config&quot;, &quot;crd&quot;, &quot;bases&quot;)},
      }
  
      cfg, err := testEnv.Start()
      Expect(err).NotTo(HaveOccurred())
      Expect(cfg).NotTo(BeNil())
  
      err = mybatch.AddToScheme(scheme.Scheme)
      Expect(err).NotTo(HaveOccurred())
  
      // +kubebuilder:scaffold:scheme
  
      k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
          Scheme: scheme.Scheme,
      })
      Expect(err).ToNot(HaveOccurred())
  
      err = (&amp;MyBatchReconciler{
          Client: k8sManager.GetClient(),
          Log:    ctrl.Log.WithName(&quot;controllers&quot;).WithName(&quot;MyBatch&quot;),
      }).SetupWithManager(k8sManager)
      Expect(err).ToNot(HaveOccurred())
  
      go func() {
          err = k8sManager.Start(ctrl.SetupSignalHandler())
          Expect(err).ToNot(HaveOccurred())
      }()
  
      k8sClient = k8sManager.GetClient()
      Expect(k8sClient).ToNot(BeNil())
  
      close(done)
  }, 60)
  
  var _ = AfterSuite(func() {
      By(&quot;tearing down the test environment&quot;)
      err := testEnv.Stop()
      Expect(err).NotTo(HaveOccurred())
  })
  </code></pre>
  <p>Here's the test block that causes it to fail. I have a second <code>Describe</code> block (not shown here), which tests some of the business logic outside of the <code>Reconcile</code> function, and that works fine.</p>
  <pre><code>package controllers
  
  import (
      &quot;context&quot;
      &quot;time&quot;
  
      . &quot;github.com/onsi/ginkgo&quot;
      . &quot;github.com/onsi/gomega&quot;
      corev1 &quot;k8s.io/api/core/v1&quot;
      metav1 &quot;k8s.io/apimachinery/pkg/apis/meta/v1&quot;
      &quot;k8s.io/apimachinery/pkg/types&quot;
  
      &quot;github.com/jarcoal/httpmock&quot;
      mybatch &quot;mycorp.com/mybatch-operator/api/v1alpha1&quot;
  )
  
  var _ = Describe(&quot;BatchController&quot;, func() {
      Describe(&quot;Reconcile&quot;, func() {
          // Define utility constants for object names and testing timeouts/durations and intervals.
          const (
              BatchName      = &quot;test-batch&quot;
              BatchNamespace = &quot;default&quot;
              BatchImage     = &quot;mycorp/mockserver:latest&quot;
  
              timeout  = time.Second * 10
              duration = time.Second * 10
              interval = time.Millisecond * 250
          )
  
          Context(&quot;When deploying MyBatch&quot;, func() {
              It(&quot;Should create a new Batch instance&quot;, func() {
                  ctx := context.Background()
  
                  // Define stub Batch
                  testCR := &amp;mybatch.MyBatch{
                      TypeMeta: metav1.TypeMeta{
                          APIVersion: &quot;mybatch.mycorp.com/v1alpha1&quot;,
                          Kind:       &quot;MyBatch&quot;,
                      },
                      ObjectMeta: metav1.ObjectMeta{
                          Name:      BatchName,
                          Namespace: BatchNamespace,
                      },
                      Spec: mybatch.MyBatchSpec{
                          Replicas: 1,
                          StatusCheck: mybatch.StatusCheck{
                              Url:         &quot;http://mycorp.com&quot;,
                              Endpoint:    &quot;/rest/jobs/jobexecutions/active&quot;,
                              PollSeconds: 20,
                          },
                          Image: BatchImage,
                          PodSpec: corev1.PodSpec{
                              // For simplicity, we only fill out the required fields.
                              Containers: []corev1.Container{
                                  {
                                      Name:  &quot;test-container&quot;,
                                      Image: BatchImage,
                                  },
                              },
                              RestartPolicy: corev1.RestartPolicyAlways,
                          },
                      },
                  }
  
                  Expect(k8sClient.Create(ctx, testCR)).Should(Succeed())
  
                  lookupKey := types.NamespacedName{Name: BatchName, Namespace: BatchNamespace}
                  createdBatch := &amp;mybatch.MyBatch{}
  
                  // We'll need to retry getting this newly created Batch, given that creation may not immediately happen.
                  Eventually(func() bool {
                      err := k8sClient.Get(ctx, lookupKey, createdBatch)
                      if err != nil {
                          return false
                      }
                      return true
                  }, timeout, interval).Should(BeTrue())
                  // Check the container name
                  Expect(createdBatch.Spec.PodSpec.Containers[0].Name).Should(Equal(BatchName))
              })
          })
      })
  })
  </code></pre>
  <p>Is there something I'm missing here that's preventing <code>Scheme</code> from being properly initialized? I have to admit that I don't really understand much of the code around the <code>Scheme</code>. I'm happy to show additional code if it will help.</p>