Race detection in table driven test - unit-testing

I have a table-driven test and init var(worker) outside table loop. I run the test with go test -raсe and added t.Parallel() and no race condition was detected. Can I assume that my test free of race condition:
//This mock could be in a separate file.
type mockWorker struct {
}
// implment our Worker iterface
func(md mockWorker)Work()error{
return nil
}
type mockDoer struct{
Error error
Worker worker
}
// implment our Doer iterface
func(md mockDoer)Do()error{
if err := md.Worker.Work(); err != nil {
return err
}
return md.Error
}
func TestBusinessDoer(t *testing.T){
t.Parallel()
worker := mockWorker{}
cases := []struct{
Name string
ExpectError bool
Error error
}{
{
Name:"test does business logic",
ExpectError : false,
Error : nil,
},
{
Name:"test fails when dependency errors",
ExpectError : true,
Error : errors.New("an error"),
},
}
for _,td := range cases{
t.Run(td.Name, func (t *testing.T){
doer := mockDoer{Error: td.Error, Worker: worker}
err := mything.BusinessDoer(doer)
if td.ExpectError && err == nil{
t.Fatalf("expected an error but got none")
}
if ! td.ExpectError && err != nil{
t.Fatalf("did not expect an error but got one %s ", err.Error())
}
})
}
}

I think you need to run the sub tests in parallel.
Can you call t.Parallel() inside the subtests and check ?
td := td
t.Run(td.Name, func (t *testing.T){
t.Parallel()
doer := mockDoer{Error: td.Error, Worker: worker}
err := mything.BusinessDoer(doer)
if td.ExpectError && err == nil{
t.Fatalf("expected an error but got none")
}
if ! td.ExpectError && err != nil{
t.Fatalf("did not expect an error but got one %s ", err.Error())
}
})
Some links to refer to make it parallel:
https://gist.github.com/posener/92a55c4cd441fc5e5e85f27bca008721
https://rakyll.org/parallelize-test-tables/

Related

Golang how to test function that return channel type?

I try to test function StartP,
Expect that Start() should be called 1 times, Done() should be called 1 times
but I have trouble that test will block when run this step <-ps.Done()
I expect <-ps.Done() return nil
How can I test function that return chan type?
// production code
func (s *vService) StartP(ctx context.Context, reason string) error {
ps, err := s.factory.CreateVService(ctx)
if err != nil {
return err
}
ps.Start(reason)
err = <-ps.Done() // code stop here to wait ? how can i test ?
if err != nil {
return err
}
return nil
}
// test code
func Test_StartP(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockPService := mockpservice.NewMockPInterface(mockCtrl)
vService := &vService {
factory: &servicefactory.FakeServiceFactory{
MockPService: mockPService
}
}
mockPService.EXPECT().Start("reason").Times(1).Return()
mockPService.EXPECT().Done().Times(1).DoAndReturn(func() chan error {
return nil
})
err := vService.StartP(context.Background(), "reason")
assert.Equal(t, nil, err)
}
I use gomock to mock the PServiceInterface
// interface
type PServiceInterface interface {
Start(reason string)
Done() <-chan error
}
gomock gen this function
func (m *MockProvisionServiceInterface) Done() <-chan error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Done")
ret0, _ := ret[0].(<-chan error)
fmt.Println(ret0,".....mock Done()")
return ret0
}
// I also try this
mockProvisionService.EXPECT().Done().Times(1).DoAndReturn( func() chan error {
fmt.Println("DoAndReturn...err nil")
ch := make(chan error, 1)
ch <- nil
return ch
})
The following shows, I think, the minimum code to implement your test goals.
It does not use any mocking framework because in my experience they tend to obfuscate the test intent, require everybody in the team to learn how to use them and are not needed, at least in Go. One could also wonder what the test is actually testing...
First, let's add some missing production code:
type factoryInterface interface {
CreateVService(ctx context.Context) (PServiceInterface, error)
}
type vService struct {
factory factoryInterface
}
And now the test code, in three parts: the factory, the mock, and the test.
The test factory:
type testFactory struct {
mock PServiceInterface
}
func (f *testFactory) CreateVService(ctx context.Context) (PServiceInterface, error) {
return f.mock, nil
}
The mock:
type ServiceMock struct {
records []string
}
func (sm *ServiceMock) Start(reason string) {
sm.records = append(sm.records, "start")
}
func (sm *ServiceMock) Done() <-chan error {
sm.records = append(sm.records, "done")
ch := make(chan error)
close(ch)
return ch
}
And finally the test:
func TestWithMock(t *testing.T) {
mock := ServiceMock{}
sut := &vService{factory: &testFactory{&mock}}
err := sut.StartP(context.Background(), "banana")
if err != nil {
t.Fatalf("StartP: have: %s; want: no error", err)
}
if have, want := len(mock.records), 2; have != want {
t.Fatalf("number of mock calls: have: %v; want: %v", have, want)
}
if have, want := mock.records[0], "start"; have != want {
t.Fatalf("mock call 1: have: %v; want: %v", have, want)
}
if have, want := mock.records[1], "done"; have != want {
t.Fatalf("mock call 2: have: %v; want: %v", have, want)
}
}
The three assertions on the mock call sequence can be collapsed into one, comparing directly the slice []string{"start", "done"}, if one is using a test library such as the excellent assert package of https://github.com/gotestyourself/gotest.tools
I found the answer, root cause is DoAndReturn something wrong.
Func type should be <-chan error, not chan error
mockProvisionService.EXPECT().Done().Times(1).DoAndReturn( func() <-chan error{
fmt.Println("DoAndReturn...err nil")
ch := make(chan error, 1)
ch <- nil
return ch
})

Trigger http client error for testing with httptest

I have a simple function which takes a URL and fetches the response:
func getUrl(url string) (string, error) {
var theClient = &http.Client{Timeout: 12 * time.Second}
resp, err := theClient.Get(url)
if err != nil {
return "", err
}
defer r.Body.Close()
body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
return "", readErr
}
return string(body), nil
}
Now, I want to trigger an error on the theClient.Get(url) line but I don't know how to. I can trigger an error on the ReadAll() line, by returning no response but with content-length:2.
How can I trigger an error on the theClient.Get(url) line for my unit test?
func TestGetUrl(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", "2")
}))
defer server.Close()
gotContent, gotErr := getUrl(server.URL)
wantErr := "unexpected EOF"
if gotErr == nil || gotErr.Error() != wantErr {
t.Errorf("got err %v; wanted %s", gotErr, wantErr)
}
}
Easiest way is to simply pass an invalid URL:
_, err := http.Get("clearly not a valid url")
fmt.Println("Got error:", err != nil) // Got error: true
Another option is to make it timeout by sleeping in your httptest.Server handler, but that doesn't seem like a very nice idea (but you will be able to assert that it was called in the first place).

Golang Unit Testing Erorr when test function with query db

i start learning unit test on golang, i have do a simple test than it work, but try on real case, i found some errors, this is my test code:
test code
func TestGetAllCompetency(t *testing.T) {
_, err := competencyService.GetAllCompetency()
if err != nil {
t.Error("failed to get competency")
}
}
GetAllCompetency Code
func GetAllCompetency() ([]models.Competency, error) {
data := []models.Competency{}
res := database.Conn.
Table("competency").Order("name").
Where("competency.is_delete = ?", false).
Find(&data)
err := res.Error
if err != nil {
log.Println("[Error] competencyService.GetAllData : ", err)
return []models.Competency{}, err
}
return data, nil
}

Channel race condition

I'm trying to write tests for a simple program that reads events from the inotify bus, filters them, then puts them on a channel. I have seen no issues when actually running the code, but in the last line of the following test, it will occasionally deadlock (about 30% of the time) on reading from that channel, even though as best I can tell, the event is always being put on the channel.
Here is the function:
func eventFilter(watcher *rfsnotify.RWatcher, excludes []string, out chan<- fsnotify.Event) {
for {
select {
case event := <-watcher.Events:
log.Debug(fmt.Sprintf("Got event %v", event))
if isExcluded(event.Name, excludes) {
log.Info("Ignoring excluded file: %v", event)
} else if isRelevantOp(event) {
log.Info(fmt.Sprintf("Handling event %v", event))
out <- event
} else {
log.Info(fmt.Sprintf("Ignoring event %v", event))
}
case err := <-watcher.Errors:
log.Error(fmt.Sprintf("Error: %v", err))
}
}
}
Here is the testing code:
var hook *logrusTest.Hook
var testDir string
var rWatcher *rfsnotify.RWatcher
func TestMain(m *testing.M) {
// initialize bad globals, amongst other things
log.SetLevel(log.DebugLevel)
hook = logrusTest.NewGlobal()
// Create directory to watch
testDir = tempMkdir()
defer os.RemoveAll(testDir)
rWatcher, _ = rfsnotify.NewWatcher()
defer rWatcher.Close()
err := rWatcher.AddRecursive(testDir)
if err != nil {
log.Fatal("Failed to add watcher: ", err)
}
os.Exit(m.Run())
}
func TestEventFilterMove(t *testing.T) {
ch := make(chan fsnotify.Event, 10)
go eventFilter(rWatcher, []string{"test"}, ch)
testFileSrc := filepath.Join(testDir, "TestFsnotifyEvents.testfileSrc")
testFileDest := filepath.Join(testDir, "TestFsnotifyEvents.testfileDest")
f, err := os.OpenFile(testFileSrc, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
t.Fatalf("creating test file failed: %s", err)
}
f.WriteString("data")
f.Sync()
hook.Reset()
err = os.Rename(testFileSrc, testFileDest)
if err != nil {
t.Fatalf("renaming test file failed: %s", err)
}
time.Sleep(100 * time.Millisecond)
expectedLogs := []string{
fmt.Sprintf("Got event \"%v\": CREATE|UPDATE", testFileDest),
fmt.Sprintf("Handling event \"%v\": CREATE|UPDATE", testFileDest),
fmt.Sprintf("Got event \"%v\": RENAME", testFileSrc),
fmt.Sprintf("Ignoring event \"%v\": RENAME", testFileSrc),
}
var actualLogs []string
for _, logEntry := range hook.AllEntries() {
actualLogs = append(actualLogs, logEntry.Message)
}
assert.ElementsMatch(t, actualLogs, expectedLogs)
hook.Reset()
_ = <-ch
assert.Equal(t, 0, len(ch))
}

How to write unit test for http request in golang?

I don't know how to test the http response given in the code below.
func getVolDetails(volName string, obj interface{}) error {
addr := os.Getenv("MAPI_ADDR")
if addr == "" {
err := errors.New("MAPI_ADDR environment variable not set")
fmt.Println(err)
return err
}
url := addr + "/path/to/somepage/" + volName
client := &http.Client{
Timeout: timeout,
}
resp, err := client.Get(url)
if resp != nil {
if resp.StatusCode == 500 {
fmt.Printf("VSM %s not found\n", volName)
return err
} else if resp.StatusCode == 503 {
fmt.Println("server not reachable")
return err
}
} else {
fmt.Println("server not reachable")
return err
}
if err != nil {
fmt.Println(err)
return err
}
defer resp.Body.Close()
return json.NewDecoder(resp.Body).Decode(obj)
}
With the help of some references i wrote unit test for this which is given below
func TestGetVolDetails(t *testing.T) {
var (
volume v1.Volume
server *httptest.Server
)
tests := map[string]struct {
volumeName string
err error
}{
"TestOne": {"vol", nil},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
response := `{"metadata":{"annotations":{"vsm.openebs.io/targetportals":"10.98.65.136:3260","vsm.openebs.io/cluster-i ps":"10.98.65.136","openebs.io/jiva-iqn":"iqn.2016-09.com.openebs.jiva:vol","deployment.kubernetes.io/revision":"1","openebs.io/storage-pool" :"default","vsm.openebs.io/replica-count":"1","openebs.io/jiva-controller-status":"Running","openebs.io/volume-monitor":"false","openebs.io/r eplica-container-status":"Running","openebs.io/jiva-controller-cluster-ip":"10.98.65.136","openebs.io/jiva-replica-status":"Running","vsm.ope nebs.io/iqn":"iqn.2016-09.com.openebs.jiva:vol","openebs.io/capacity":"2G","openebs.io/jiva-controller-ips":"10.36.0.6","openebs.io/jiva-repl ica-ips":"10.36.0.7","vsm.openebs.io/replica-status":"Running","vsm.openebs.io/controller-status":"Running","openebs.io/controller-container- status":"Running","vsm.openebs.io/replica-ips":"10.36.0.7","openebs.io/jiva-target-portal":"10.98.65.136:3260","openebs.io/volume-type":"jiva ","openebs.io/jiva-replica-count":"1","vsm.openebs.io/volume-size":"2G","vsm.openebs.io/controller-ips":"10.36.0.6"},"creationTimestamp":null ,"labels":{},"name":"vol"},"status":{"Message":"","Phase":"Running","Reason":""}}`
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, response)
}))
os.Setenv("MAPI_ADDR", "http://"+server.URL)
if got := GetVolDetails(tt.volumeName, &volume); got != tt.err {
t.Fatalf("GetVolDetails(%v) => got %v, want %v ", tt.volumeName, got, tt.err)
}
defer server.Close()
})
}
}
Where response is the response i'm getting from the server. This gives me always different errors.
got invalid character '<' looking for beginning of value, want <nil>
got Get http://www.HugeDomains.com: net/http: request canceled (Client.Timeout exceeded while awaiting headers), want <nil>
What am I doing wrong?
Edit:
Updated the code with SOME_ADDR to MAPI_ADDR which was done while posting question. Please don't be confused with that, problem remains as it is.
You are getting a timeout but you are not specifying what timeout is set to. I suspect that this is not a time.Duration object and that is causing your timeout. There are a few other issues as well. To get this to work I did:
Change the function being called in the test to getVolDetails to match the code (not the lower case g)
Set the Timeout when creating the client to Timeout: time.Second * 10
Remove the "http://"+ from the os.Setenv("MAPI_ADDR", "http://"+server.URL) line
Corrected code is:
var timeout time.Duration = time.Second * 1000
func getVolDetails(volName string, obj interface{}) error {
addr := os.Getenv("MAPI_ADDR")
if addr == "" {
err := errors.New("MAPI_ADDR environment variable not set")
fmt.Println(err)
return err
}
url := addr + "/path/to/somepage/" + volName
client := &http.Client{
Timeout: timeout,
}
resp, err := client.Get(url)
if resp != nil {
if resp.StatusCode == 500 {
fmt.Printf("VSM %s not found\n", volName)
return err
} else if resp.StatusCode == 503 {
fmt.Println("server not reachable")
return err
}
} else {
fmt.Println("server not reachable")
return err
}
if err != nil {
fmt.Println(err)
return err
}
defer resp.Body.Close()
return json.NewDecoder(resp.Body).Decode(obj)
}
and test:
func TestGetVolDetails(t *testing.T) {
var (
volume v1.Volume
server *httptest.Server
)
tests := map[string]struct {
volumeName string
err error
}{
"TestOne": {"vol", nil},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
response := `{"metadata":{"annotations":{"vsm.openebs.io/targetportals":"10.98.65.136:3260","vsm.openebs.io/cluster-i ps":"10.98.65.136","openebs.io/jiva-iqn":"iqn.2016-09.com.openebs.jiva:vol","deployment.kubernetes.io/revision":"1","openebs.io/storage-pool" :"default","vsm.openebs.io/replica-count":"1","openebs.io/jiva-controller-status":"Running","openebs.io/volume-monitor":"false","openebs.io/r eplica-container-status":"Running","openebs.io/jiva-controller-cluster-ip":"10.98.65.136","openebs.io/jiva-replica-status":"Running","vsm.ope nebs.io/iqn":"iqn.2016-09.com.openebs.jiva:vol","openebs.io/capacity":"2G","openebs.io/jiva-controller-ips":"10.36.0.6","openebs.io/jiva-repl ica-ips":"10.36.0.7","vsm.openebs.io/replica-status":"Running","vsm.openebs.io/controller-status":"Running","openebs.io/controller-container- status":"Running","vsm.openebs.io/replica-ips":"10.36.0.7","openebs.io/jiva-target-portal":"10.98.65.136:3260","openebs.io/volume-type":"jiva ","openebs.io/jiva-replica-count":"1","vsm.openebs.io/volume-size":"2G","vsm.openebs.io/controller-ips":"10.36.0.6"},"creationTimestamp":null ,"labels":{},"name":"vol"},"status":{"Message":"","Phase":"Running","Reason":""}}`
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, response)
}))
os.Setenv("MAPI_ADDR", server.URL)
if got := getVolDetails(tt.volumeName, &volume); got != tt.err {
t.Fatalf("GetVolDetails(%v) => got %v, want %v ", tt.volumeName, got, tt.err)
}
defer server.Close()
})
}
}