Remix.run Logo
spenczar5 2 days ago

"But accepting the full S3Client here ties UploadReport to an interface that’s too broad. A fake must implement all the methods just to satisfy it."

This isn't really true. Your mock inplementation can embed the interface, but only implement the one required method. Calling the unimplemented methods will panic, but that's not unreasonable for mocks.

That is:

    type mockS3 struct {
        S3Client
    }

    func (m mockS3) PutObject(...) {
        ...
    }
You don't have to implement all the other methods.

Defining a zillion interfaces, all the permutations of methods in use, makes it hard to cone up with good names, and thus hard to read.

lenkite a day ago | parent | next [-]

Not to mention, introducing all the permutations of methods as separate interfaces on the "consumer side" means extreme combinatorial explosion of interfaces. It is far better to judge the most common patterns and make single-method interfaces for these on the provider side.

Lots of such frequently-quoted Go "principles" are invalid and are regularly broken within the standard library and many popular Go projects. And if you point them out, you will be snootily advised by the Go gurus on /r/golang or even here on HN that every principle has exceptions. (Even if there are tens of thousands of such exceptions).

skybrian 2 days ago | parent | prev | next [-]

While you can do that, having unused methods that don't work is a footgun. It's cleaner if they don't exist at all.

the_gipsy 2 days ago | parent | prev [-]

Is this pattern commonly used? Any drawbacks?

Sounds much better than the interface boilerplate if it's just for the sake of testing.

jgdxno 2 days ago | parent | next [-]

At work we use it heavily. You don't really see "a zillion interfaces" after a while, only set of dependencies of a package which is easy to read, and easy to understand.

"makes it hard to cone up with good names" is not really a problem, if you have a `CreateRequest` method you name the interface `RequestCreator`. If you have a request CRUD interface, it's probably a `RequestRepository`.

The benefits outweigh the drawbacks 10 to one. The most rewarding thing about this pattern is how easy it is to split up large implementations, and _keep_ them small.

durbatuluk 2 days ago | parent | prev [-]

Any method you forget to overwrite from the embed struct gives a false "impression" you can call any method from mockS3. Most of time code inside test will be:

    // embedded S3Client not properly initialized
    mock := mockS3{}
    // somewhere inside the business logic
    s3.UploadReport(...) // surprise
Go is flexible, you can define a complete interface at producer and consumers still can use their own interface only with required methods if they want.