-3

I have been following this answer trying to mock open. I have got exactly no where.

This is the test code I have:

func (m mockedFS) Open(name string) (file, error) {
    if m.reportErrOpen {
        return nil, errors.New("Fake failure")
    }
    mockedFile := mockIORead{}
    mockedFile.On("ReadAll", mock.AnythingOfType("[]uint8")).Return(0, fmt.Errorf("error reading"))
    mockedFile.On("Read", mock.AnythingOfType("[]byte")).Return(0, errors.New("NON"))
    return mockedFile, nil
}

type mockIORead struct {
    mock.Mock
    reportErr  bool  // Tells if this mocked FS should return error in our tests
    reportSize int64 // Tells what size should Stat() report in our test
}

func (m mockIORead) Read(b []byte) (n int, err error) {
    if m.reportErr {
        return 0, errors.New("A fake failure")
    }
    s := "Fear the old blood"
    copy(b[:], s)
    return 0, nil
}

func (m mockIORead) Close() error {
    return nil
}

func (m mockIORead) ReadAt([]byte, int64) (int, error) {
    return 0, nil
}

func (m mockIORead) Seek(int64, int) (int64, error) {
    return 0, nil
}

func (m mockIORead) Stat() (os.FileInfo, error) {
    if m.reportErr {
        return nil, os.ErrNotExist
    }
    return mockedFileInfo{size: m.reportSize}, nil
}

func TestOok(t *testing.T) {
    oldFs := fs
    // Create and "install" mocked fs:
    mfs := &mockedFS{}
    fs = mfs
    // Make sure fs is restored after this test:
    defer func() {
        fs = oldFs
    }()
    mfs.reportErr = false
    mfs.reportErrOpen = false
    token, err := Ook("fake")
    assert.NotNil(t, err)
    assert.Equal(t, "Fear the old blood", token)
}

And this is the code under test:

func Ook(name string) (string, error) {

    _, err := fs.Stat(name)
    if err != nil {
        return "", nil
    }

    file, err := fs.Open(name)
    if err != nil {
        return "", errors.Wrap(err, "Cannot open token file")
    }
    defer file.Close()

    _, err = ioutil.ReadAll(file)
    fmt.Print("PING\n")
    if err != nil {
        return "", errors.Wrap(err, "Could not read token")
    }
    return "Fear the old blood", nil
    //return string(token), nil
}

What the hell am I doing wrong?

Sardathrion - against SE abuse
  • 17,269
  • 27
  • 101
  • 156
  • 2
    "What the hell am I doing wrong?" Probably not the answer you are looking for but the main problem is that you try to mock the filesystem at all. If your test really run on a machine _without_ _filesystem_ this is a necessary thing to do but it is also _really_ uncommon. Just read from the fs. This is fine, even in tests. No need to mock everything. (You do not mock RAM and CPU either). – Volker Sep 21 '20 at 12:36
  • 3
    What is `fs` you use in your `Ook()` function? Also `mockIORead.Read()` must return the number of read bytes, the number returned by `copy()`! – icza Sep 21 '20 at 12:36
  • @icza The same one used in your [answer](https://stackoverflow.com/a/43914455/232794). – Sardathrion - against SE abuse Sep 21 '20 at 12:46

1 Answers1

2

The first error is that your mockIORead.Read() returns wrong values. It must return the number of read bytes (bytes written to the slice argument) (e.g. what copy() would return).

Next, mockIORead.Read() must be stateful! Reader.Read() might be called several times, there is no guarantee the passed slice can accommodate all the data you want to return (via the passed b slice).

So mockIORead must store the data you want to return, and it must remember how much of them has been delivered so far, so the next Read() call can continue from there.

An easy implementation of this is to utilize bytes.Buffer:

type mockIORead struct {
    mock.Mock
    reportErr  bool  // Tells if this mocked FS should return error in our tests
    reportSize int64 // Tells what size should Stat() report in our test

    content *bytes.Buffer
}

When returning such a mockIORead, initialize content with the content you wish to return:

func (m mockedFS) Open(name string) (file, error) {
    if m.reportErrOpen {
        return nil, errors.New("Fake failure")
    }
    mockedFile := mockIORead{
        content: bytes.NewBufferString("Fear the old blood"),
    }

    return mockedFile, nil
}

And thanks to the available bytes.Buffer.Read() method, the mockIORead.Read() implementation can be as simple as this:

func (m mockIORead) Read(b []byte) (n int, err error) {
    if m.reportErr {
        return 0, errors.New("A fake failure")
    }
    return m.content.Read(b)
}

The Ook() function itself should not try to "stat" as you haven't mocked it (and so calling the original os.Stat() will likely yield an error for the "fake" file name used in the test):

func Ook(name string) (string, error) {
    file, err := fs.Open(name)
    if err != nil {
        return "", errors.Wrap(err, "Cannot open token file")
    }
    defer file.Close()

    token, err := ioutil.ReadAll(file)
    fmt.Print("PING\n")
    if err != nil {
        return "", errors.Wrap(err, "Could not read token")
    }
    return string(token), nil
}

And the testing code:

func TestOok(t *testing.T) {
    oldFs := fs
    // Create and "install" mocked fs:
    mfs := &mockedFS{}
    fs = mfs
    // Make sure fs is restored after this test:
    defer func() {
        fs = oldFs
    }()

    mfs.reportErr = false
    mfs.reportErrOpen = false
    token, err := Ook("fake")
    assert.Nil(t, err)
    assert.Equal(t, "Fear the old blood", token)
}

Which yields a successful ("OK") test.

icza
  • 389,944
  • 63
  • 907
  • 827