gointerfacemockinggomock

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:


Solution

  • 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)