Posted:
I’ve been working on pal, a CLI tool to generate shell aliases for each of your projects. It needs to interact with the filesystem to read a config file and write a .pal
file that includes the aliases. I wanted to start writing tests for reading/writing files but struggled to figure out how to do that until I came across afero, a filesystem abstraction written by Steve Francia.
To use afero, I needed to call it instead of os.
For example, in my utils, I have a function that checks if a file is missing like this:
func FileMissing(path string) bool {
_, e := os.Stat(path)
return errors.Is(e, os.ErrNotExist)
}
To be able to call afero, I need to pass in the afero.Fs
object as a parameter and update the call to os.Stat(path)
.
func FileMissing(fs afero.Fs, path string) bool {
_, e := fs.Stat(path)
return errors.Is(e, os.ErrNotExist)
}
I then needed to update the usages of FileMissing
to pass in the filesystem:
var AppFs = afero.NewOsFs()
if pkg.FileMissing(AppFs, "/file/path/here") {
//
}
Once I did that, I was able to start writing tests.
First, I created a struct to handle each of test case.
type fileMissingTestCase struct {
path string
expected bool
}
Then wrote the test function and added two test cases using the struct.
func TestFileMissing(t *testing.T) {
cases := []fileMissingTestCase{
{"tmp/foo.txt", true},
{"tmp/bar.txt", false},
}
// ...
}
This has the two test cases where I’ll be checking if “tmp/foo.txt” and “tmp/bar.txt” exist. In my test, I set it up where the “tmp/bar.txt” file exists but “tmp/foo.txt” does not.
appFs := afero.NewMemMapFs()
is creating a new in-memory filesystem to use within this test. Then I’m creating a “temp” directory and writing the “bar.txt” file.
appFs := afero.NewMemMapFs()
mkdirErr := appFs.Mkdir("temp", 0o755)
if mkdirErr != nil {
t.Errorf("Mkdir Error: %q", mkdirErr)
}
writeFileErr := afero.WriteFile(appFs, "tmp/bar.txt", []byte("bar file"), 0o644)
if writeFileErr != nil {
t.Errorf("WriteFile Error: %q", writeFileErr)
}
Now, I’m able to iterate over the test cases and check that I get the expected value from pkg.FileMissing()
.
for _, tc := range cases {
t.Run(fmt.Sprintf("Path: %q", tc.path), func(t *testing.T) {
got := FileMissing(appFs, tc.path)
if got != tc.expected {
t.Errorf("Expected 'false', but got '%v'", got)
}
})
}
Here’s the full test:
type fileMissingTestCase struct {
path string
expected bool
}
func TestFileMissing(t *testing.T) {
cases := []fileMissingTestCase{
{"tmp/foo.txt", true},
{"tmp/bar.txt", false},
}
appFs := afero.NewMemMapFs()
mkdirErr := appFs.Mkdir("temp", 0o755)
if mkdirErr != nil {
t.Errorf("Mkdir Error: %q", mkdirErr)
}
writeFileErr := afero.WriteFile(appFs, "tmp/bar.txt", []byte("bar file"), 0o644)
if writeFileErr != nil {
t.Errorf("WriteFile Error: %q", writeFileErr)
}
for _, tc := range cases {
t.Run(fmt.Sprintf("Path: %q", tc.path), func(t *testing.T) {
got := FileMissing(appFs, tc.path)
if got != tc.expected {
t.Errorf("Expected '%v', but got '%v'", tc.expected, got)
}
})
}
}