WinXaito
WinXaito

Reputation: 41

How to mock net.Interface

I'm trying to mock net.Interface in Go, I use net.Interfaces() and I want to have a fixed return. But net.Interface is not an interface, so I can't mock it with gomock.

Maybe I'm wrong in the way I test.

Here is the method I want to test:

const InterfaceWlan = "wlan0"
const InterfaceEthernet = "eth0"

var netInterfaces = net.Interfaces

func GetIpAddress() (net.IP, error) {
    // On récupère la liste des interfaces
    ifaces, err := netInterfaces()
    if err != nil {
        return nil, err
    }

    // On parcours la liste des interfaces
    for _, i := range ifaces {
        // Seul l'interface Wlan0 ou Eth0 nous intéresse
        if i.Name == InterfaceWlan || i.Name == InterfaceEthernet {
            // On récupère les adresses IP associé (généralement IPv4 et IPv6)
            addrs, err := i.Addrs()

            // Some treatments on addrs...
        }
    }

    return nil, errors.New("network: ip not found")
}

Here is the test I wrote for the moment

func TestGetIpAddress(t *testing.T) {
    netInterfaces = func() ([]net.Interface, error) {
        // I can create net.Interface{}, but I can't redefine 
        // method `Addrs` on net.Interface
    }
    
    address, err := GetIpAddress()
    if err != nil {
        t.Errorf("GetIpAddress: error = %v", err)
    }

    if address == nil {
        t.Errorf("GetIpAddress: errror = address ip is nil")
    }
}

Minimal reproductible example:

Upvotes: 1

Views: 2099

Answers (2)

funnyDev
funnyDev

Reputation: 86

IMO. you can injection function net.Interface And function getAddrs for get []net.Addrs into GetIpAddress

type NetworkHandler struct {
    GetInterfaces func() ([]net.Interface,error)
    GetAddrsFromInterface func(p net.Interface) ([]net.Addr,error)
}
func GetIpAddress(networkHandler NetworkHandler) (net.IP,error) {
    // On récupère la liste des interfaces
    ifaces, err := networkHandler.GetInterfaces()
    if err != nil {
        return nil, err
    }

    // On parcours la liste des interfaces
    for _, i := range ifaces {
        // Seul l'interface Wlan0 ou Eth0 nous intéresse
        if i.Name == InterfaceWlan || i.Name == InterfaceEthernet {
            // On récupère les adresses IP associé (généralement IPv4 et IPv6)
            _, err := networkHandler.GetAddrsFromInterface(i)

            // ex
            if err != nil {
                return nil, err
            }
            // Some treatments on addrs...
        }
    }

    return nil, errors.New("network: ip not found")
}

ON Test code

func TestGetIpAddress(t *testing.T) {
    t.Run("should return error when cannot get address from net.interface", func(t *testing.T) {
        result, err := GetIpAddress(NetworkHandler{
            GetInterfaces:         mockGetInterfaces,
            GetAddrsFromInterface: mockGetAddrs,
        })

        assert.Nil(t, result)
        assert.Error(t, err)
        assert.Equal(t, "cannot get addrs",err.Error())
    })
}
func mockGetInterfaces() ([]net.Interface,error) {
    return []net.Interface{
        {Name:         "wlan0"},
        {Name:         "eth0"}},nil
}

// stub behavior when calling net.Interface{}.Addrs()
func mockGetAddrs(i net.Interface) ([]net.Addr,error) {
    return nil, errors.New("cannot get addrs")
}

on usage

func main() {
    GetIpAddress(NetworkHandler{
        GetInterfaces:         net.Interfaces,
        GetAddrsFromInterface: func(p net.Interface) ([]net.Addr, error) {
            return p.Addrs()
        },
    })
}

Upvotes: 1

bcmills
bcmills

Reputation: 5187

You can use a method expression to bind the method to a variable of function type, in much the same way that you are already binding the net.Interfaces function to a variable:

var (
    netInterfaces     = net.Interfaces
    netInterfaceAddrs = (*net.Interface).Addrs
)

func GetIpAddress() (net.IP, error) {
    …
            // Get IPs (mock method Addrs ?)
            addrs, err := netInterfaceAddrs(&i)
    …
}

Then, in the test, you can update the binding in the same way:

func TestGetIpAddress(t *testing.T) {
    …
    netInterfaceAddrs = func(i *net.Interface) ([]net.Addr, error) {
        return []net.Addr{}, nil
    }
    …
}

(https://play.golang.org/p/rqb0MDclTe2)


That said, I would recommend factoring out the mocked methods into a struct type instead of overwriting global variables. That allows the test to run in parallel, and also allows downstream users of your package to write their own tests without mutating global state.

// A NetEnumerator enumerates local IP addresses.
type NetEnumerator struct {
    Interfaces     func() ([]net.Interface, error)
    InterfaceAddrs func(*net.Interface) ([]net.Addr, error)
}

// DefaultEnumerator returns a NetEnumerator that uses the default
// implementations from the net package.
func DefaultEnumerator() NetEnumerator {
    return NetEnumerator{
        Interfaces:     net.Interfaces,
        InterfaceAddrs: (*net.Interface).Addrs,
    }
}

func GetIpAddress(e NetEnumerator) (net.IP, error) {
    …
}

(https://play.golang.org/p/PLIXuOpH3ra)

Upvotes: 5

Related Questions