<p>When using the Kubernetes <a href="https://pkg.go.dev/k8s.io/client-go/kubernetes/fake" rel="nofollow noreferrer">Fake Client</a> to write unit tests, I noticed that it fails to create two identical objects which have their <a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#ObjectMeta" rel="nofollow noreferrer"><code>ObjectMeta.GenerateName</code></a> field set to some string. A real cluster accepts this specification and generates a unique name for each object.</p>
<p>Running the following test code:</p>
<pre><code>package main

import (
    &quot;context&quot;
    &quot;testing&quot;

    &quot;github.com/stretchr/testify/assert&quot;
    corev1 &quot;k8s.io/api/core/v1&quot;
    metav1 &quot;k8s.io/apimachinery/pkg/apis/meta/v1&quot;
    &quot;k8s.io/client-go/kubernetes/fake&quot;
)

func TestFake(t *testing.T) {
    ctx := context.Background()
    client := fake.NewSimpleClientset()

    _, err := client.CoreV1().Secrets(&quot;default&quot;).Create(ctx, &amp;corev1.Secret{
        ObjectMeta: metav1.ObjectMeta{
            GenerateName: &quot;generated&quot;,
        },
        StringData: map[string]string{&quot;foo&quot;: &quot;bar&quot;},
    }, metav1.CreateOptions{})
    assert.NoError(t, err)

    _, err = client.CoreV1().Secrets(&quot;default&quot;).Create(ctx, &amp;corev1.Secret{
        ObjectMeta: metav1.ObjectMeta{
            GenerateName: &quot;generated&quot;,
        },
        StringData: map[string]string{&quot;foo&quot;: &quot;bar&quot;},
    }, metav1.CreateOptions{})
    assert.NoError(t, err)
}
</code></pre>
<p>fails with</p>
<pre><code>--- FAIL: TestFake (0.00s)
    /Users/mihaitodor/Projects/kubernetes/main_test.go:44: 
            Error Trace:    main_test.go:44
            Error:          Received unexpected error:
                            secrets &quot;&quot; already exists
            Test:           TestFake
FAIL
FAIL    kubernetes  0.401s
FAIL
</code></pre>

Mihai Todor
<p>According to <a href="https://github.com/kubernetes/client-go/issues/439#issuecomment-403867107" rel="nofollow noreferrer">this</a> GitHub issue comment:</p>
<blockquote>
<p>the fake clientset doesn't attempt to duplicate server-side behavior
like validation, name generation, uid assignment, etc. if you want to
test things like that, you can add reactors to mock that behavior.</p>
</blockquote>
<p>To add the required reactor, we can insert the following code before creating the <code>corev1.Secret</code> objects:</p>
<pre><code>client.PrependReactor(
    &quot;create&quot;, &quot;*&quot;,
    func(action k8sTesting.Action) (handled bool, ret runtime.Object, err error) {
        ret = action.(k8sTesting.CreateAction).GetObject()
        meta, ok := ret.(metav1.Object)
        if !ok {
            return
        }

        if meta.GetName() == &quot;&quot; &amp;&amp; meta.GetGenerateName() != &quot;&quot; {
            meta.SetName(names.SimpleNameGenerator.GenerateName(meta.GetGenerateName()))
        }

        return
    },
)
</code></pre>
<p>There are a few gotchas in there:</p>
<ul>
<li>The <code>Clientset</code> contains an embedded <a href="https://pkg.go.dev/k8s.io/client-go/testing#Fake" rel="nofollow noreferrer"><code>Fake</code></a> structure which has the <a href="https://pkg.go.dev/k8s.io/client-go/testing#Fake.PrependReactor" rel="nofollow noreferrer"><code>PrependReactor</code></a> method we need to call for this use case (there are a few others). This code <a href="https://github.com/kubernetes/client-go/blob/d6c83109f030902f150f03f252311d2749cb6094/testing/fake.go#L140-L145" rel="nofollow noreferrer">here</a> is invoked when creating such objects.</li>
<li>The <code>PrependReactor</code> method has 3 parameters: <code>verb</code>, <code>resource</code> and <code>reaction</code>. For <code>verb</code>, <code>resource</code>, I couldn't find any named constants, so, in this case, &quot;create&quot; and &quot;secrets&quot; (strange that it's not &quot;secret&quot;) seem to be the correct values for them if we want to be super-specific, but setting <code>resource</code> to &quot;*&quot; should be acceptable in this case.</li>
<li>The <code>reaction</code> parameter is of type <a href="https://pkg.go.dev/k8s.io/client-go/testing#ReactionFunc" rel="nofollow noreferrer">ReactionFunc</a>, which takes an <a href="https://pkg.go.dev/k8s.io/client-go/testing#Action" rel="nofollow noreferrer"><code>Action</code></a> as a parameter and returns <code>handled</code>, <code>ret</code> and <code>err</code>. After some digging, I noticed that the <code>action</code> parameter will be cast to <a href="https://pkg.go.dev/k8s.io/client-go/testing#CreateAction" rel="nofollow noreferrer"><code>CreateAction</code></a>, which has the <code>GetObject()</code> method that returns a <code>runtime.Object</code> instance, which can be cast to <a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Object" rel="nofollow noreferrer"><code>metav1.Object</code></a>. This interface allows us to get and set the various metadata fields of the underlying object. After setting the object <code>Name</code> field as needed, we have to return <code>handled = false</code>, <code>ret = mutatedObject</code> and <code>err = nil</code> to instruct the calling code to execute the remaining reactors.</li>
<li>Digging through the <code>apiserver</code> code, I noticed that the <code>ObjectMeta.Name</code> field <a href="https://github.com/kubernetes/apiserver/blob/f0b4663d4cd5caceddb64fd239053d29208104cd/pkg/registry/rest/create.go#L112-L114" rel="nofollow noreferrer">is generated</a> from the <code>ObjectMeta.GenerateName</code> field using the <a href="https://pkg.go.dev/k8s.io/apiserver/pkg/storage/names" rel="nofollow noreferrer"><code>names.SimpleNameGenerator.GenerateName</code></a> utility.</li>
</ul>
