I've found that unless I write code in golang in a way that was intentionally written to be unit tested, it is very hard to bolt on unit testing after the fact. I'm stuck with effectively just integration testing. I don't envy you working on legacy code.
struct Foo {
}
func (f *Foo) doSomething() {
client := pkg.NewClient()
client.doHttpRequest()
}
vs.
struct Foo {
client Client
}
func (f *Foo) doSomething() {
f.client.doHttpRequest()
}
func NewFoo(client Client) *Foo {
return &Foo{ client: client }
}
var FooModule = fx.Provide(NewFoo)
then to use it...
struct Bar {
foo *Foo
}
func NewBar(foo *Foo) *Bar {
return &Bar{foo: foo}
}
var BarModule = fx.Provide(NewBar)
how do you unit test the first example? There is no easy way to that without having the client do things special internally, which leads to a rats nest of issues and doesn't scale. Easier to just pass in a mock client and make sure the right http request/responses are being done.
fx kind of highly encourages the second approach for all of your code. It is a bit more boilerplate, but by default, I'm always going to be thinking about passing my dependencies around instead of just being lazy and doing NewClient in the function.
I also get to take advantage of the fact that Client is just magically passed into my constructor. If there is some error doing so (like I forget to Provide), then fx will give me errors at start up. My dependency chain is clear. It just feels nicer to me.
Once again, of course I can write it the second way without fx... but it is kind of nice that every time I want to use Foo, I don't have to type NewFoo() in a constructor... I just reference *Foo.
By the way, mockery [1] generates super easy to use mocks for exactly this workflow.
[1] https://github.com/vektra/mockery