Git Product home page Git Product logo

Comments (3)

twmb avatar twmb commented on June 17, 2024 3

In this case, no. Returning an interface type itself is not really standard Go (barring some small things occasionally, such as an io.Reader or hash.Hash).

If I define and return an interface in kgo, then technically any function I add to the client needs to be added to the interface, and technically this is a breaking change. I just wrote about mocks and their purpose on a different thread recently, so I'll copy and rephrase some of that here.


This blog post walks through some aspects of what follows, but imo the most important aspect could be described more, and I do so below. Dave Cheney's blog post (linked within that linked blog post) also has a section, the "Interface Segregation Principle", which describes this further. There's also other small writeups about this, e.g. "Use many small interfaces to model dependencies".

The purpose of a mock is to make it such that dependents of the api (in this case, your tests) can inject behavior and test it. But, these dependents are not interested in the entire kgo.Client API. They are interested in some type that provides a subset of the client's API -- probably Produce or PollFetches or Request. The tests do not care if the type has other functions (e.g., Broker). The standard way to define this contract in Go is by having the dependency declare "I want something that provides the two functions I am interested in", i.e., to have your tests define the interface they accept, where the interface is small and local to it.

Over time, the client API may grow to 30 or 40 methods. Your test code is not interested in those other methods. It is still only interested in a type that provides the two functions it needs. So, rather than accepting some kgo.ClientInterface type, your test code could locally define something like:

type KafkaClient interface {
  Produce(context.Context, *kgo.Record, promise...)
}

and use your local KafkaClient interface everywhere. This will allow you to inject what you want.

By having your tests be dependent on an external mock/interface (kgo.ClientInterface), it creates unnecessary dependencies. Your mocks would forever be dependent on the kgo.ClientInterface type, meaning if I introduce functions, I break your mocks. Instead, if you define interfaces locally, your mocks need not know anything of the client, and my client can add methods independently with no worry about breaking external code.


Mocks have their place, but imo not usually, and often not in tests for a package's own code. They may be useful when we, when writing package A, use a package B that takes an interface we do not want to implement, and the mock provides hooks for us to check invariants to ensure we are using the middle package B properly. For example, if I use a SQL package B, I could pass it a SQL mock in tests and then later check the mock to ensure my usage of B was correct. In contrast, per the above writeup, package B itself does not need to define that it takes the SQL mock. It just needs to take a few functions. Package B's tests do not need to use a mock, they can just use a few custom functions with a custom test interface type. The end user of package B is doing the orchestrating.


Hopefully this paraphrasing / rephrasing somewhat answered the question?

I do hope to eventually add a kmem or something package that is a tiny recreation of Kafka in memory, but that's different from this issue. It would be useful in tests, though, so you don't need to talk to a Kafka cluster and can just use a kgo.Client blindly against the memory recreation.

from franz-go.

twmb avatar twmb commented on June 17, 2024

Let me know what you think -- for now I'm going to close this issue, but depending on your thoughts it may be worth reopening?

from franz-go.

skoo25 avatar skoo25 commented on June 17, 2024

Makes sense, thanks!

from franz-go.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.