pulumi icon indicating copy to clipboard operation
pulumi copied to clipboard

Mocking the pulumi automation API

Open jamesla opened this issue 3 years ago • 3 comments

I'm using the Pulumi automation API in an application that i'm writing however I am having trouble writing tests that mock the functionality. Do mocks already exist for the pulumi automation API and if so where can I find examples?

Functionality I want to mock:

const stack = await LocalWorkspace.createOrSelectStack({
      stackName: this.name,
      workDir: this.destinationRoot(),
    }, {
      secretsProvider: `awskms://${keyId}?region=${this.region}`,
      projectSettings: {
        name: this.name,
        runtime: 'nodejs',
        backend: {
          url: `s3://${bucket}`,
        },
      },
    });

jamesla avatar Aug 19 '21 00:08 jamesla

We do not have mocks available for Automation API, we don't mock for our own testing and have a CLI installed and authenticated to run with our tests. But this is a nice suggestion!

Unsure what your intentions are with needing to mock out automation api, but here are a few possible suggestions you could try:

  • Using a local backend for testing if the concern is polluting your pulumi org or forcing traffic over the wire
  • Mocking using spyOn from jest
  • Implementing a "test" impl of the Workspace interface (this might be a lot of work, and we have not tried this)

emiliza avatar Aug 19 '21 20:08 emiliza

I just came here because I have the same requirement. I think the last point, to implement each function of the Workspace interface wouldn't be too hard using the gomock generator (at least for go). However this comes with another issue: The workspace is always created within utility functions, such as here, in the auto.UpsertStackInlineSource function: https://github.com/pulumi/pulumi/blob/f4b76a901a5ab9e89723c9ed9c78c4f86bde0024/sdk/go/auto/local_workspace.go#L1310

Even recreating the few lines of this function in my own code doesn't easily work, because it uses private sub functions such as this one: https://github.com/pulumi/pulumi/blob/f4b76a901a5ab9e89723c9ed9c78c4f86bde0024/sdk/go/auto/local_workspace.go#L1302

So all this would need to be recreated as well. It would be awesome if we could just hand over a Workspace pointer to functions like UpsertStackInlineSource. That way we could send either a mock or the real one.

sfc-gh-jlangefeld avatar Apr 19 '24 21:04 sfc-gh-jlangefeld

Upon thinking about this a little further, I actually found a solution that works for me. So posting here in case anyone else stumbles upon this:

At the place where I'm calling auto.NewLocalWorkspace I changed it to this (truncated to essentials):

import (
 	// other imports

	"github.com/pulumi/pulumi/sdk/v3/go/auto"
	_ "go.uber.org/mock/mockgen/model"
)

//go:generate mockgen -destination=./mocks/workspace.go -package mocks github.com/pulumi/pulumi/sdk/v3/go/auto Workspace

var (
	autoUpsertStackInlineSource = auto.UpsertStackInlineSource
)

func Get(ctx context.Context) (auto.Stack, error) {
	var stack auto.Stack

	// create stack
	stack, err :=  autoUpsertStackInlineSource(
		ctx,
		auto.FullyQualifiedStackName(config.Org, config.ProjectName, stackName),
		config.ProjectName,
		modules.Run,
	)

	// rest of code

	return stack, err
}

Then in my _test.go file I'm doing this (truncated to essentials):

func TestSomething(t *testing.T) {
	tests := map[string]struct{}{
		"happy case":      {},
		// other test cases
	}

	for name := range tests {
		t.Run(name, func(t *testing.T) {
			ctrl := gomock.NewController(t)
			w := mocks.NewMockWorkspace(ctrl)

			// mock whatever workspace function your code is calling
			w.EXPECT().CreateStack(gomock.Any(), gomock.Any()).Return(nil)
			w.EXPECT().Program().Return(func(ctx *pulumi.Context) error { return nil })

			autoUpsertStackInlineSource = func(ctx context.Context) (auto.Stack, error) {
				return auto.NewStack(ctx, "test-stack", w)
			}
			Run(nil, nil)
		})
	}
}

The workspace mock can just be generated via go generate ./...

So I'm basically doing the dependency injection on one level higher, via autoUpsertStackInlineSource = auto.UpsertStackInlineSource and overwriting that function in the test using a mock workspace to init the stack.

With that I'd actually say there's no todo on this issue.

sfc-gh-jlangefeld avatar Apr 19 '24 22:04 sfc-gh-jlangefeld