Unit test method which need a file - unit-testing

I've the following function and I need to create a unit test for it
package main
import (
"fmt"
"io/ioutil"
"os"
)
type Source struct {
Path string
}
type fileReader interface {
readOneFile() ([]byte, error)
}
func(s Source) readOneFile() ([]byte, error) {
cwd, err := os.Getwd()
file, err := ioutil.ReadFile(fmt.Sprintf("%s/file.txt", cwd))
if err != nil {
return nil, fmt.Errorf("erro reading file : %s", err.Error())
}
return file, err
}
The problem is that I use path to file in the method, what it the best practice in go to create a unit test for this kind of functions ?

Tests will run in the directory that contains the tests
So Getwd will give the path to that directory
The filename for test data in files in test directories should begin with underscore _
However, your program needs a file called "file.txt" . To support testing this filepath that does not start with _ create the file file.txt in (for example) /tmp, do a chdir to /tmp immediately before running the test and let the test pick up the file that was just made

For writing unit test you need to create a file within same package with fileName_test.go
Suppose your file name read.go so your test file name should be read_test.go.
read_test.go
package main
import (
"testing"
"fmt"
)
func TestReadOneFile(t *testing.T) {
var a Source
f, err := a.readOneFile()
if err != nil {
t.Errorf("incorrect")
} else {
fmt.Println("passed")
}
}
Here you have to named your test function name with Test as prefix and need to import package testing.
After creating the unit test you can check the code coverage by running below two commands:
1. go test --coverprofile coverage.out
2. go tool cover -html=coverage.out

Related

Read go.mod and detect the version of project

Mostly, go.mod file looks something like this :
module <module_name>
go 1.16
require (...)
Now, I want to extract the version value 1.16 in another golang project
I read the file and stored it in a buffer.
buf, err := ioutil.ReadFile(goMODfile)
if err != nil {
return false, err.Error()
}
I guess FindString() or MatchString() functions can help me out here, but I am not sure how !
Instead of regexp you could just use "golang.org/x/mod/modfile" to parse the go.mod file's contents.
f, err := modfile.Parse("go.mod", file_bytes, nil)
if err != nil {
panic(err)
}
fmt.Println(f.Go.Version)
https://play.golang.org/p/XETDzMcTwS_S
If you have to use regexp, then you could do the following:
re := regexp.MustCompile(`(?m)^go (\d+\.\d+(?:\.\d+)?)$`)
match := re.FindSubmatch(file_bytes)
version := string(match[1])
https://play.golang.org/p/L5-LM67cvgP
The simple way to report information about a Go module is to use go list. You can use the -m flag to list properties of modules and the -f flag to report specific fields. If no specific module is given, go list -m reports information about the main module (containing the current working directory).
For example, to list the GoVersion for the main module:
$ go list -f {{.GoVersion}} -m

How to read files outside main.go, and make it work on AWS lambda

On AWS Lambda, I want to use CSV file outside main.go
Of course this works when I follow these steps.
cd ~/go/src/lambda-go-sample
GOOS=linux go build main.go
zip main.zip main
upload to Lambda and test => "Hello ƛ!"
main.go
package main
import (
"github.com/aws/aws-lambda-go/lambda"
)
func hello() (string, error) {
return "Hello ƛ!", nil
}
func main() {
lambda.Start(hello)
}
Now I add sample.csv on the same directory of main.go ,and add code to read it.
lambda-go-sample
|- main.go
|- sample.csv
main.go
...
func hello()([]string ,error){
queries, err := readCSV("sample.csv")
return queries, err
}
func main(){
lambda.Start(hello)
}
func readCSV(fileName string) (queries []string, err error) {
f, err := os.Open(fileName)
if err != nil {
fmt.Fprintln(os.Stderr, "Failed to read CSV file", err)
return nil, err
}
r := csv.NewReader(f)
for {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
fmt.Fprintln(os.Stderr, "Failed to read CSV record", err)
return nil, err
}
companyName := strings.Replace(record[1], "INC", "", -1)
queries = append(queries, companyName+"CM")
}
return queries, nil
}
Then, when I follow the same steps as above, this error occurs on console.
{
"errorMessage": "open sample: no such file or directory",
"errorType": "PathError"
}
How can I solve this problem??
I have no idea whether that "lambda" thing has this concept, but when a process works in a contemporary commodity OS it has the concept of "current directory" or "working directory" or "current working directory"; any attempt to access a file using a relative (not absolute) name will have that name interpreted as relative to the CWD of the current process.
Note two further things:
When launching the process, the OS is free to set its CWD to whatever value it wants.
It may not be immediately obvious for when you run a process in an interactive shell, the shell sets the CWD of the process to the directory which is current for the shell, and the OS itself does not interfere.
The process can change its CWD at runtime.
Note one more thing: that "lambda" thing might actually have a concept similar but orthogonal to the CWD: for instance, it might require the process to query some environment variable (or other information resource) to know where its "assets" are placed.
So, to step back a little bit. You might start with os.Getwd to get the process' CWD and then use path/filepath.Join to obtain the full name of your CSV file and then attempt to read it.
If that fails, you might want to dig deeper on what runtime environment that "lambda" thing actually provides to the processes it runs.
If I were you, I'd start with this bit.

How do i open all files of the format "test*.txt" in golang

If I give a flag (in golang) as 'test*.txt' (as a CLI argument), I need to open all files of the said format (example: test.txt, test1.txt, test2.txt).
I am thinking of doing a regex match with all the files present in the folder. Is there a better way?
You can use the filepath.Glob function:
package main
import (
"fmt"
"path/filepath"
)
func main() {
matches, _ := filepath.Glob("test*.txt")
for _, p := range matches {
fmt.Println(p)
}
}

Golang: tests and working directory

I'm writing some unit tests for my application in Go. The tests fail however because it cannot find the configuration files. Normally the binary looks for the configuration files in the working directory under the path conf/*.conf.
I figured that browsing to the directory that has conf/ and running go test in it would solve it, but it still reports that the file system cannot find the path specified.
How can I tell go test to use a certain directory as the working directory so that the tests may actually be executed?
You may be able to use the Caller to get the path to the current test source file, like this:
package sample
import (
"testing"
"runtime"
"fmt"
)
func TestGetFilename(t *testing.T) {
_, filename, _, _ := runtime.Caller(0)
t.Logf("Current test filename: %s", filename)
}
I do not believe this is possible. I have not been able to find documentation stating this explicitly, but I believe go test always uses the package directory (containing the go source files) as the working directory.
As a workaround, I compiled the test and execute the test from the current directory.
go test -c && ./<mypackage>.test
Or, if you want a generic command that you can use, you can rename the test file with -o option.
go test -c -o xyz.test && ./xyz.test
While not really convenient, you can always pass it as a command line variable, for example :
package blah_test
import (
"flag"
"fmt"
"os"
"testing"
)
var (
cwd_arg = flag.String("cwd", "", "set cwd")
)
func init() {
flag.Parse()
if *cwd_arg != "" {
if err := os.Chdir(*cwd_arg); err != nil {
fmt.Println("Chdir error:", err)
}
}
}
func TestBlah(t *testing.T) {
t.Errorf("cwd: %+q", *cwd_arg)
}
Then run it like :
┌─ oneofone#Oa [/tmp]
└──➜ go test . -cwd="$PWD"
--- FAIL: TestBlah (0.00 seconds)
blah_test.go:16: cwd: "/tmp"
No matter where the work directory is. It must be under your project Dir. So my solution is
wd, _ := os.Getwd()
for !strings.HasSuffix(wd, "<yourProjectDirName>") {
wd = filepath.Dir(wd)
}
raw, err := ioutil.ReadFile(fmt.Sprintf("%s/src/conf/conf.dev.json", wd))
Your path should always start from your project Dir. Every time you read the file in a package and accessed by main.go or your another package unit test. It will always work.
You can use the os package.
You would want to do something like this
func TestMyFunction(t *testing.T) {
os.Chdir("./path")
//TEST FUNCTION
os.Chdir("..")
}
There are several possibilities in the os package.
To add init function into *_test.go under your test package.
Test package will run this function before test function start.
func init() {
_, filename, _, _ := runtime.Caller(0)
// The ".." may change depending on you folder structure
dir := path.Join(path.Dir(filename), "..")
err := os.Chdir(dir)
if err != nil {
panic(err)
}
}
I know this is an old question but I had the same problem trying to use migrations for the database on my tests, and maybe this solution helps someone.
Since there is no native way of getting the project directory, you could identify some file or directory that you know it's only in the root of the project (in my case, it was the relative directory database/migrations). Once you have this unique relative directory, you could have a function like the following to obtain the project root directory. It just gets the current working directory (assuming it's inside the project's directory) and starts to navigate all the way up until it finds a dir that has the relative directory you know it's on the root of the project:
func FindMyRootDir() string {
workingDirectory, err := os.Getwd()
if err != nil {
panic(err)
}
lastDir := workingDirectory
myUniqueRelativePath := "database/migrations"
for {
currentPath := fmt.Sprintf("%s/%s", lastDir, myUniqueRelativePath)
fi, err := os.Stat(currentPath)
if err == nil {
switch mode := fi.Mode(); {
case mode.IsDir():
return currentPath
}
}
newDir := filepath.Dir(lastDir)
// Ooops, we couldn't find the root dir. Check that your "myUniqueRelativePath" really exists
if newDir == "/" || newDir == lastDir {
return ""
}
lastDir = newDir
}
}
Of course it's not the most beautiful solution, but it works.
I've had a similar problem and found the solution on this blog
Basically you can change the folder that the test is running using a similar function:
package main
import (
"os"
"path"
"runtime"
)
func MakeFunctionRunOnRootFolder() {
_, filename, _, _ := runtime.Caller(0)
// The ".." may change depending on you folder structure
dir := path.Join(path.Dir(filename), "..")
err := os.Chdir(dir)
if err != nil {
panic(err)
}
}
Go 1.20 is getting new -C arguments for "go subcommands" so this should help:
go test -C directory/ ...
It's a common practice in Go to place test fixtures in same package inside testdata folder.
Some examples from standard library:
debug/elf
net/http
image
Also, there is a post from Dave Cheney, where he suggests following code:
f, err := os.Open("testdata/somefixture.json")
I currently use a neat solution for this problem, instead of opening the file directly by calling os.Open(), I use the embed package in a smart way:
First I create a global variable in my root package called:
//go:embed config/* otherdirectories/*
var RootFS embed.FS
Then I just open the files inside my tests by using this global variable, e.g.:
func TestOpenConfig(t *testing.T) {
configFile, err := rootpkg.RootFS.ReadFile("config/env")
if err != nil {
t.Fatalf("unable to open config/env file: %s", err)
}
if string(configFile) != "FOO=bar\n" {
t.Fatalf("config file contents differ from expected: %s", string(configFile))
}
}
This is a neat trick because now you can always work with relative paths from your root package, which is what I used to do in other programming languages.
Of course, this has the restriction that you will need to import your root package, which depending on your package layout might not be ideal because of cyclic imports. If this is your case you might just create a embed.go file inside the config directory itself and call
your configs by name.
One other drawback is that you are embedding test files in your binary, this is probably ok if your test files are not very big, like megabytes big, so I don't really mind this issue.
I also created a repository for illustrating this solution:
https://github.com/VinGarcia/golang-reading-files-from-tests
I would use an Environment Variable for the location of your application. It seems to be the best way when running go tools, as test programs can be run from a temporary location.
// get home dir of app, use MYAPPHOME env var if present, else executable dir.
func exeDir() string {
dir, exists := os.LookupEnv("MYAPPHOME")
if exists {
return dir
} else {
ex, err := os.Executable()
if err != nil {
panic(err)
}
exPath := path.Dir(ex)
return exPath
}
}

Package visibility in Go Unit Tests

Given the following code file (named server.go) in Go:
package glimpse
func SplitHeader() string {
return "hi there"
}
and the accompanying test file (server_test.go):
package glimpse
import (
"testing"
)
func TestSplitHeader(t *testing.T) {
answer := SplitHeader()
if answer == "" {
t.Error("No return value")
}
}
Why is it the following command:
go test server_test.go
returns
# command-line-arguments
./server_test.go:9: undefined: SplitHeader
I'm certainly missing something catastrophically obvious.
Use only
$ go test
from within the package directory to perform testing. If you name specific files as an argument to go test, then only those file will be considered for the build of the test binary. That explains the 'undefined' error.
As an alternative, use "import path" as an argument to go test instead, for example
$ go test foo.com/glimpse