Monkey patching between packages

So far, we have looked at monkey patching a private global variable or function for the purposes of testing inside our data package. But what happens if we want to test other packages? Wouldn't it be nice to decouple the business logic layer from the database too? That would certainly stop our business logic layer tests from breaking for unrelated events, such as optimizing our SQL queries.

Again, we are faced with a dilemma; we could start large-scale refactoring, but as we've mentioned before, it's a lot of work and a lot of risk, especially without tests to keep us out of trouble. Let's look at the most straightforward business logic package we have, the get package:

// Getter will attempt to load a person.
// It can return an error caused by the data layer or
// when the requested person is not found
type Getter struct {
}

// Do will perform the get
func (g *Getter) Do(ID int) (*data.Person, error) {
// load person from the data layer
person, err := data.Load(ID)
if err != nil {
if err == data.ErrNotFound {
// By converting the error we are encapsulating the
// implementation details from our users.
return nil, errPersonNotFound
}
return nil, err
}

return person, err
}

As you can see, this function does very little beyond loading the person from the database. You could argue therefore that it does not need to exist; don't worry, we will be giving it more responsibility later on.

So how do we test this without the database? The first thing that comes to mind might be to monkey patch the database pool or the getDatabase() function as we did before.

This would work, but it would be sloppy and pollute the public API for the data package with things that we don't want production code using, the very definition of test-induced damage. It would also do nothing to decouple this package from the internal implementation of the data package. In fact, it would make it worse. Any change to the implementation of the data package would likely break our test for this package.

Another aspect to consider is that we can make any alteration we want because the service is small and we own all the code. This is often not the case; the package could be owned by another team, it could be part of an external dependency, or even part of the standard library. It's better, therefore, to get into the habit of keeping our changes local to the package we are working on.

With that in mind, we can adopt a trick we looked at briefly in the previous section, Advantages of monkey patching. Let's intercept the call from the get package to the data package, as shown in the following code:

// Getter will attempt to load a person.
// It can return an error caused by the data layer or
// when the requested person is not found
type Getter struct {
}

// Do will perform the get
func (g *Getter) Do(ID int) (*data.Person, error) {
// load person from the data layer
person, err := loader(ID)
if err != nil {
if err == data.ErrNotFound {
// By converting the error we are hiding the
// implementation details from our users.
return nil, errPersonNotFound
}
return nil, err
}

return person, err
}

// this function as a variable allows us to Monkey Patch during testing
var loader = data.Load

Now, we can intercept the calls with monkey patching, as shown in the following code:

func TestGetter_Do_happyPath(t *testing.T) {
// inputs
ID := 1234

// monkey patch calls to the data package
defer func(original func(ID int) (*data.Person, error)) {
// restore original
loader = original
}(loader)

// replace method
loader = func(ID int) (*data.Person, error) {
result := &data.Person{
ID: 1234,
FullName: "Doug",
}
var resultErr error

return result, resultErr
}
// end of monkey patch

// call method
getter := &Getter{}
person, err := getter.Do(ID)

// validate expectations
require.NoError(t, err)
assert.Equal(t, ID, person.ID)
assert.Equal(t, "Doug", person.FullName)
}

Now, our test is not dependent on the database or any internal implementation details of the data package. While we have not entirely decoupled the packages, we have significantly reduced the number of things that must happen correctly for the tests in the get package to pass. This is arguably one of the points of DI by monkey patching, reducing the ways tests could break by reducing the dependence on outside factors and increasing the focus of the tests.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.229.111