Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interoperability with gomock #131

Merged
merged 5 commits into from
Nov 23, 2022
Merged

Interoperability with gomock #131

merged 5 commits into from
Nov 23, 2022

Conversation

nfx
Copy link
Contributor

@nfx nfx commented Nov 3, 2022

This PR addresses two major issues

Interoperability with gomock

When developing large applications, you find yourself in need of mocking APIs. For Go, there's gomock framework for code generating testing mocks. In this small example, we'll show how to use gomock with Databricks SDK for Go.

Please read through dbfs_test.go test example.

Declaring which mocks to generate

//go:generate go run github.com/golang/mock/mockgen@latest -package=mocks -destination=mocks/dbfs.go github.com/databricks/databricks-sdk-go/service/dbfs DbfsService
  • go run github.com/golang/mock/mockgen@latest downloads and executes the latest version of mockgen command
  • -package=mocks instructs to generate mocks in the mocks package
  • -destination=mocks/dbfs.go instructs to create dbfs.go file with mock stubs.
  • github.com/databricks/databricks-sdk-go/service/dbfs tells which Databricks package to look services in.
  • DbfsService tells which services to generate mocks for.

Initializing gomock

Every test needs the following preamble:

ctrl := gomock.NewController(t)
defer ctrl.Finish()

Mocking individual methods with gomock

Every actual method call must be mocked for the test to pass:

mockDbfs := mocks.NewMockDbfsService(ctrl)
mockDbfs.EXPECT().Create(gomock.Any(), gomock.Eq(dbfs.Create{
    Path:      "/a/b/c",
    Overwrite: true,
})).Return(&dbfs.CreateResponse{
    Handle: 123,
}, nil)

Testing idioms with Databricks SDK for Go

You can stub out the auth with the databricks.NewMockConfig(nil) helper function. Every service has a public property with the name of the service plus Service suffix. You have to manually set the stubs for every service that is called in unit tests.

w := workspaces.New(databricks.NewMockConfig(nil))
w.Dbfs.DbfsService = mockDbfs

Running this example

  1. Run go mod tidy in this folder to create go.sum file to pick dependency versions.
  2. Run go mod vendor to download dependencies into vendor/ directory.
  3. Run go generate ./... to create mocks/ directory.
  4. Run go test ./... to invoke tests with mocks.

@nfx nfx changed the title Initial commit for mocking Support for gomock generator Nov 3, 2022
@nfx nfx changed the title Support for gomock generator Split high-level client from low-level client Nov 21, 2022
@nfx
Copy link
Contributor Author

nfx commented Nov 21, 2022

Pros:

"SDK Features" and "low level" clients are physically separated, that allows for mocking.
image

Risks

It isn't easy to read godoc, and, subsequently, pkg.go.dev documentation - the place, where developers go for documentation. In this example, methods get hidden
image

@nfx
Copy link
Contributor Author

nfx commented Nov 21, 2022

though, generating dummy proxy methods like this one does solve the documentation issue:
image

image

@nfx nfx marked this pull request as ready for review November 21, 2022 21:33
@nfx nfx requested a review from a team November 21, 2022 21:33
@nfx nfx changed the title Split high-level client from low-level client Interoperability with gomock Nov 21, 2022
@nfx nfx linked an issue Nov 21, 2022 that may be closed by this pull request
* cannot yet mock higher-level wrappers, like `*AndWait` because of generics are not yet supported by gomock (even in v1.7.0-rc.1)
databricks/openapi/code/load.go Outdated Show resolved Hide resolved
databricks/openapi/code/load.go Show resolved Hide resolved
databricks/openapi/code/load.go Show resolved Hide resolved
examples/mocking/README.md Show resolved Hide resolved
@@ -53,22 +53,22 @@ func TestMwsAccNetworks(t *testing.T) {
if !a.Config.IsAccountsClient() || !a.Config.IsAws() {
t.SkipNow()
}
netw, err := a.Networks.CreateNetworkConfig(ctx, deployment.CreateNetworkRequest{
netw, err := a.NetworkConfigurations.CreateNetworkConfig(ctx, deployment.CreateNetworkRequest{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for this PR: the stuttering here can/should be removed: CreateNetworkConfig -> Create

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are certain risks with it, like "automatic name clashes" - I'll see if it's possible

service/.codegen/accounts.go.tmpl Outdated Show resolved Hide resolved
service/.codegen/api.go.tmpl Outdated Show resolved Hide resolved
service/.codegen/api.go.tmpl Outdated Show resolved Hide resolved
@nfx nfx requested a review from pietern November 23, 2022 11:34
@nfx nfx merged commit 3e307bb into main Nov 23, 2022
@nfx nfx deleted the mocking branch November 23, 2022 11:53
type ReposAPI struct {
client *client.DatabricksClient
// impl contains low-level REST API interface, that could be overridden
// through WithImpl(ReposService)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WithImpl -> SetImpl, "with" implies its scoped somehow (same as #167)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Split high-level generated interface from low-level direct API mapping
2 participants