Repository: go-kratos/kratos
Pull request #2145 (2022-06-30 02:47:34)

feat(contrib): add nacos unit test and remove unused function
**Description (what this PR does / why we need it):**
add nacos unit test

**Which issue(s) this PR fixes (resolves / be part of):**
part of [#2135](https://github.com/go-kratos/kratos/issues/2135)

Other special notes for the reviewers:

codecov-commenter: # [Codecov](https://codecov.io/gh/go-kratos/kratos/pull/2145?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) Report
> Merging [#2145](https://codecov.io/gh/go-kratos/kratos/pull/2145?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) (b817ef7) into [main](https://codecov.io/gh/go-kratos/kratos/commit/4ca25e46799ca1579d9e877e5be23f454ca23299?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) (4ca25e4) will **decrease** coverage by `0.05%`.
> The diff coverage is `n/a`.

```diff
@@            Coverage Diff             @@
##             main    #2145      +/-   ##
==========================================
- Coverage   79.08%   79.03%   -0.06%     
==========================================
  Files          84       84              
  Lines        3696     3696              
==========================================
- Hits         2923     2921       -2     
- Misses        560      562       +2     
  Partials      213      213              
```


| [Impacted Files](https://codecov.io/gh/go-kratos/kratos/pull/2145?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) | Coverage Δ | |
|---|---|---|
| [internal/context/context.go](https://codecov.io/gh/go-kratos/kratos/pull/2145/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-aW50ZXJuYWwvY29udGV4dC9jb250ZXh0Lmdv) | `97.01% <0.00%> (-2.99%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/go-kratos/kratos/pull/2145?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/go-kratos/kratos/pull/2145?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos). Last update [4ca25e4...b817ef7](https://codecov.io/gh/go-kratos/kratos/pull/2145?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos).


Patch:
diff --git a/cmd/kratos/internal/proto/server/template.go b/cmd/kratos/internal/proto/server/template.go
index 1d60ebba..732947ae 100644
--- a/cmd/kratos/internal/proto/server/template.go
+++ b/cmd/kratos/internal/proto/server/template.go
@@ -5,6 +5,7 @@ import (
 	"html/template"
 )
 
+//nolint:lll
 var serviceTemplate = `
 {{- /* delete empty line */ -}}
 package service
@@ -34,8 +35,7 @@ func New{{ .Service }}Service() *{{ .Service }}Service {
 {{- $s1 := "google.protobuf.Empty" }}
 {{ range .Methods }}
 {{- if eq .Type 1 }}
-func (s *{{ .Service }}Service) {{ .Name }}(ctx context.Context, req {{ if eq .Request $s1 }}*emptypb.Empty
-{{ else }}*pb.{{ .Request }}{{ end }}) ({{ if eq .Reply $s1 }}*emptypb.Empty{{ else }}*pb.{{ .Reply }}{{ end }}, error) {
+func (s *{{ .Service }}Service) {{ .Name }}(ctx context.Context, req {{ if eq .Request $s1 }}*emptypb.Empty{{ else }}*pb.{{ .Request }}{{ end }}) ({{ if eq .Reply $s1 }}*emptypb.Empty{{ else }}*pb.{{ .Reply }}{{ end }}, error) {
 	return {{ if eq .Reply $s1 }}&emptypb.Empty{}{{ else }}&pb.{{ .Reply }}{}{{ end }}, nil
 }
 
diff --git a/cmd/protoc-gen-go-http/http.go b/cmd/protoc-gen-go-http/http.go
index fca4a63c..3eb91c06 100644
--- a/cmd/protoc-gen-go-http/http.go
+++ b/cmd/protoc-gen-go-http/http.go
@@ -31,7 +31,13 @@ func generateFile(gen *protogen.Plugin, file *protogen.File, omitempty bool) *pr
 	g := gen.NewGeneratedFile(filename, file.GoImportPath)
 	g.P("// Code generated by protoc-gen-go-http. DO NOT EDIT.")
 	g.P("// versions:")
-	g.P(fmt.Sprintf("// protoc-gen-go-http %s", release))
+	g.P(fmt.Sprintf("// - protoc-gen-go-http %s", release))
+	g.P("// - protoc             ", protocVersion(gen))
+	if file.Proto.GetOptions().GetDeprecated() {
+		g.P("// ", file.Desc.Path(), " is a deprecated file.")
+	} else {
+		g.P("// source: ", file.Desc.Path())
+	}
 	g.P()
 	g.P("package ", file.GoPackageName)
 	g.P()
@@ -301,4 +307,16 @@ func isASCIIDigit(c byte) bool {
 	return '0' <= c && c <= '9'
 }
 
+func protocVersion(gen *protogen.Plugin) string {
+	v := gen.Request.GetCompilerVersion()
+	if v == nil {
+		return "(unknown)"
+	}
+	var suffix string
+	if s := v.GetSuffix(); s != "" {
+		suffix = "-" + s
+	}
+	return fmt.Sprintf("v%d.%d.%d%s", v.GetMajor(), v.GetMinor(), v.GetPatch(), suffix)
+}
+
 const deprecationComment = "// Deprecated: Do not use."
diff --git a/contrib/config/nacos/config.go b/contrib/config/nacos/config.go
index 54d4c92d..c234817c 100644
--- a/contrib/config/nacos/config.go
+++ b/contrib/config/nacos/config.go
@@ -4,28 +4,18 @@ import (
 	"context"
 	"path/filepath"
 	"strings"
-	"time"
 
-	"github.com/go-kratos/kratos/v2/config"
 	"github.com/nacos-group/nacos-sdk-go/clients/config_client"
 	"github.com/nacos-group/nacos-sdk-go/vo"
+
+	"github.com/go-kratos/kratos/v2/config"
 )
 
 type Option func(*options)
 
 type options struct {
-	endpoint string // nolint:structcheck,unused
-
-	namespaceID string // nolint:structcheck,unused
-
 	group  string
 	dataID string
-
-	timeoutMs uint64
-	logLevel  string
-
-	logDir   string
-	cacheDir string
 }
 
 // WithGroup With nacos config group.
@@ -42,34 +32,6 @@ func WithDataID(dataID string) Option {
 	}
 }
 
-// WithLogDir With nacos config group.
-func WithLogDir(logDir string) Option {
-	return func(o *options) {
-		o.logDir = logDir
-	}
-}
-
-// WithCacheDir With nacos config cache dir.
-func WithCacheDir(cacheDir string) Option {
-	return func(o *options) {
-		o.cacheDir = cacheDir
-	}
-}
-
-// WithLogLevel With nacos config log level.
-func WithLogLevel(logLevel string) Option {
-	return func(o *options) {
-		o.logLevel = logLevel
-	}
-}
-
-// WithTimeout With nacos config timeout.
-func WithTimeout(time time.Duration) Option {
-	return func(o *options) {
-		o.timeoutMs = uint64(time.Milliseconds())
-	}
-}
-
 type Config struct {
 	opts   options
 	client config_client.IConfigClient
diff --git a/contrib/config/nacos/go.mod b/contrib/config/nacos/go.mod
index c77f6657..825f993e 100644
--- a/contrib/config/nacos/go.mod
+++ b/contrib/config/nacos/go.mod
@@ -5,7 +5,7 @@ go 1.16
 require (
 	github.com/go-kratos/kratos/v2 v2.3.1
 	github.com/nacos-group/nacos-sdk-go v1.0.9
-	gopkg.in/yaml.v3 v3.0.1
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
 replace github.com/go-kratos/kratos/v2 => ../../../
diff --git a/contrib/registry/etcd/watcher.go b/contrib/registry/etcd/watcher.go
index d04f4fbd..22e63474 100644
--- a/contrib/registry/etcd/watcher.go
+++ b/contrib/registry/etcd/watcher.go
@@ -29,7 +29,7 @@ func newWatcher(ctx context.Context, key, name string, client *clientv3.Client)
 		serviceName: name,
 	}
 	w.ctx, w.cancel = context.WithCancel(ctx)
-	w.watchChan = w.watcher.Watch(w.ctx, key, clientv3.WithPrefix(), clientv3.WithRev(0))
+	w.watchChan = w.watcher.Watch(w.ctx, key, clientv3.WithPrefix(), clientv3.WithRev(0), clientv3.WithKeysOnly())
 	err := w.watcher.RequestProgress(context.Background())
 	if err != nil {
 		return nil, err


Test patch:
diff --git a/contrib/config/kubernetes/config_test.go b/contrib/config/kubernetes/config_test.go
index bd0a605a..6cdb1c98 100644
--- a/contrib/config/kubernetes/config_test.go
+++ b/contrib/config/kubernetes/config_test.go
@@ -1,18 +1,44 @@
 package kubernetes
 
 import (
+	"context"
 	"log"
 	"path/filepath"
+	"reflect"
+	"strings"
 	"testing"
 
 	"github.com/go-kratos/kratos/v2/config"
+
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/rest"
+	"k8s.io/client-go/tools/clientcmd"
 	"k8s.io/client-go/util/homedir"
 )
 
+const (
+	testKey   = "test_config.json"
+	namespace = "default"
+	name      = "test"
+)
+
+var (
+	keyPath    = strings.Join([]string{namespace, name, testKey}, "/")
+	objectMeta = metav1.ObjectMeta{
+		Name:      name,
+		Namespace: namespace,
+		Labels: map[string]string{
+			"app": "test",
+		},
+	}
+)
+
 func TestSource(t *testing.T) {
 	home := homedir.HomeDir()
 	s := NewSource(
-		Namespace("mesh"),
+		Namespace("default"),
 		LabelSelector(""),
 		KubeConfig(filepath.Join(home, ".kube", "config")),
 	)
@@ -40,3 +66,143 @@ func ExampleNewSource() {
 		log.Panic(err)
 	}
 }
+
+func TestConfig(t *testing.T) {
+	restConfig, err := rest.InClusterConfig()
+	home := homedir.HomeDir()
+
+	options := []Option{
+		Namespace(namespace),
+		LabelSelector("app=test"),
+	}
+
+	if err != nil {
+		kubeconfig := filepath.Join(home, ".kube", "config")
+		restConfig, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
+		if err != nil {
+			t.Fatal(err)
+		}
+		options = append(options, KubeConfig(kubeconfig))
+	}
+	clientSet, err := kubernetes.NewForConfig(restConfig)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	clientSetConfigMaps := clientSet.CoreV1().ConfigMaps(namespace)
+
+	source := NewSource(options...)
+	if _, err = clientSetConfigMaps.Create(context.Background(), &v1.ConfigMap{
+		ObjectMeta: objectMeta,
+		Data: map[string]string{
+			testKey: "test config",
+		},
+	}, metav1.CreateOptions{}); err != nil {
+		t.Fatal(err)
+	}
+
+	defer func() {
+		if err = clientSetConfigMaps.Delete(context.Background(), name, metav1.DeleteOptions{}); err != nil {
+			t.Error(err)
+		}
+	}()
+	kvs, err := source.Load()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(kvs) != 1 || kvs[0].Key != keyPath || string(kvs[0].Value) != "test config" {
+		t.Fatal("config error")
+	}
+
+	w, err := source.Watch()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		_ = w.Stop()
+	}()
+	// create also produce an event, discard it
+	if _, err = w.Next(); err != nil {
+		t.Fatal(err)
+	}
+
+	if _, err = clientSetConfigMaps.Update(context.Background(), &v1.ConfigMap{
+		ObjectMeta: objectMeta,
+		Data: map[string]string{
+			testKey: "new config",
+		},
+	}, metav1.UpdateOptions{}); err != nil {
+		t.Error(err)
+	}
+
+	if kvs, err = w.Next(); err != nil {
+		t.Fatal(err)
+	}
+
+	if len(kvs) != 1 || kvs[0].Key != keyPath || string(kvs[0].Value) != "new config" {
+		t.Fatal("config error")
+	}
+}
+
+func TestExtToFormat(t *testing.T) {
+	restConfig, err := rest.InClusterConfig()
+	home := homedir.HomeDir()
+
+	options := []Option{
+		Namespace(namespace),
+		LabelSelector("app=test"),
+	}
+
+	if err != nil {
+		kubeconfig := filepath.Join(home, ".kube", "config")
+		restConfig, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
+		if err != nil {
+			t.Fatal(err)
+		}
+		options = append(options, KubeConfig(kubeconfig))
+	}
+	clientSet, err := kubernetes.NewForConfig(restConfig)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	clientSetConfigMaps := clientSet.CoreV1().ConfigMaps(namespace)
+
+	tc := `{"a":1}`
+	if _, err = clientSetConfigMaps.Create(context.Background(), &v1.ConfigMap{
+		ObjectMeta: objectMeta,
+		Data: map[string]string{
+			testKey: tc,
+		},
+	}, metav1.CreateOptions{}); err != nil {
+		t.Fatal(err)
+	}
+
+	defer func() {
+		if err = clientSetConfigMaps.Delete(context.Background(), name, metav1.DeleteOptions{}); err != nil {
+			t.Error(err)
+		}
+	}()
+
+	source := NewSource(options...)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	kvs, err := source.Load()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !reflect.DeepEqual(len(kvs), 1) {
+		t.Errorf("len(kvs) = %d", len(kvs))
+	}
+	if !reflect.DeepEqual(keyPath, kvs[0].Key) {
+		t.Errorf("kvs[0].Key is %s", kvs[0].Key)
+	}
+	if !reflect.DeepEqual(tc, string(kvs[0].Value)) {
+		t.Errorf("kvs[0].Value is %s", kvs[0].Value)
+	}
+	if !reflect.DeepEqual("json", kvs[0].Format) {
+		t.Errorf("kvs[0].Format is %s", kvs[0].Format)
+	}
+}
diff --git a/contrib/config/nacos/config_test.go b/contrib/config/nacos/config_test.go
index 2fdc4f6b..2d290803 100644
--- a/contrib/config/nacos/config_test.go
+++ b/contrib/config/nacos/config_test.go
@@ -1,41 +1,20 @@
 package config
 
 import (
-	"fmt"
-	"net"
+	"reflect"
 	"testing"
 	"time"
 
 	"github.com/nacos-group/nacos-sdk-go/clients"
 	"github.com/nacos-group/nacos-sdk-go/common/constant"
 	"github.com/nacos-group/nacos-sdk-go/vo"
-	"gopkg.in/yaml.v3"
 
-	kconfig "github.com/go-kratos/kratos/v2/config"
+	"github.com/go-kratos/kratos/v2/config"
 )
 
-func getIntranetIP() string {
-	addrs, err := net.InterfaceAddrs()
-	if err != nil {
-		return "127.0.0.1"
-	}
-
-	for _, address := range addrs {
-		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
-			if ipnet.IP.To4() != nil {
-				return ipnet.IP.String()
-			}
-		}
-	}
-	return "127.0.0.1"
-}
-
-func TestGetConfig(t *testing.T) {
-	ip := getIntranetIP()
-	// ctx := context.Background()
-
+func TestConfig_Load(t *testing.T) {
 	sc := []constant.ServerConfig{
-		*constant.NewServerConfig(ip, 8848),
+		*constant.NewServerConfig("127.0.0.1", 8848),
 	}
 
 	cc := constant.ClientConfig{
@@ -48,7 +27,6 @@ func TestGetConfig(t *testing.T) {
 		LogLevel:            "debug",
 	}
 
-	// a more graceful way to create naming client
 	client, err := clients.NewConfigClient(
 		vo.NacosClientParam{
 			ClientConfig:  &cc,
@@ -58,52 +36,177 @@ func TestGetConfig(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
+	source := NewConfigSource(client, WithGroup("test"), WithDataID("test.yaml"))
 
-	dataID := "test.yaml"
-	group := "test"
-	_, err = client.PublishConfig(vo.ConfigParam{DataId: dataID, Group: group, Content: `
-logger:
-  level: info
-`})
-	if err != nil {
-		t.Fatal(err)
+	type fields struct {
+		source config.Source
 	}
-	time.Sleep(1 * time.Second)
-	c := kconfig.New(
-		kconfig.WithSource(
-			NewConfigSource(client, WithGroup(group), WithDataID(dataID)),
-		),
-		kconfig.WithDecoder(func(kv *kconfig.KeyValue, v map[string]interface{}) error {
-			return yaml.Unmarshal(kv.Value, v)
-		}),
-	)
-
-	if err = c.Load(); err != nil {
-		t.Fatal(err)
+	tests := []struct {
+		name      string
+		fields    fields
+		want      []*config.KeyValue
+		wantErr   bool
+		preFunc   func(t *testing.T)
+		deferFunc func(t *testing.T)
+	}{
+		{
+			name: "normal",
+			fields: fields{
+				source: source,
+			},
+			wantErr: false,
+			preFunc: func(t *testing.T) {
+				_, err = client.PublishConfig(vo.ConfigParam{DataId: "test.yaml", Group: "test", Content: "test: test"})
+				if err != nil {
+					t.Error(err)
+				}
+				time.Sleep(time.Second * 1)
+			},
+			deferFunc: func(t *testing.T) {
+				_, dErr := client.DeleteConfig(vo.ConfigParam{DataId: "test.yaml", Group: "test"})
+				if dErr != nil {
+					t.Error(dErr)
+				}
+			},
+			want: []*config.KeyValue{{
+				Key:    "test.yaml",
+				Value:  []byte("test: test"),
+				Format: "yaml",
+			}},
+		},
+		{
+			name: "error",
+			fields: fields{
+				source: source,
+			},
+			wantErr: false,
+			preFunc: func(t *testing.T) {
+				_, err = client.PublishConfig(vo.ConfigParam{DataId: "111.yaml", Group: "notExist", Content: "test: test"})
+				if err != nil {
+					t.Error(err)
+				}
+				time.Sleep(time.Second * 1)
+			},
+			deferFunc: func(t *testing.T) {
+				_, dErr := client.DeleteConfig(vo.ConfigParam{DataId: "111.yaml", Group: "notExist"})
+				if dErr != nil {
+					t.Error(dErr)
+				}
+			},
+			want: []*config.KeyValue{{
+				Key:    "test.yaml",
+				Value:  []byte{},
+				Format: "yaml",
+			}},
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			if test.preFunc != nil {
+				test.preFunc(t)
+			}
+			if test.deferFunc != nil {
+				defer test.deferFunc(t)
+			}
+			s := test.fields.source
+			configs, lErr := s.Load()
+			if (lErr != nil) != test.wantErr {
+				t.Errorf("Load error = %v, wantErr %v", lErr, test.wantErr)
+				t.Errorf("Load configs = %v", configs)
+				return
+			}
+			if !reflect.DeepEqual(configs, test.want) {
+				t.Errorf("Load configs = %v, want %v", configs, test.want)
+			}
+		})
 	}
+}
 
-	name, err := c.Value("logger.level").String()
-	if err != nil {
-		t.Fatal(err)
+func TestConfig_Watch(t *testing.T) {
+	sc := []constant.ServerConfig{
+		*constant.NewServerConfig("127.0.0.1", 8848),
 	}
-	fmt.Println("get value", name)
 
-	done := make(chan struct{})
-	err = c.Watch("logger.level", func(key string, value kconfig.Value) {
-		fmt.Println(key, " value change", value)
-		done <- struct{}{}
-	})
-	if err != nil {
-		t.Fatal(err)
+	cc := constant.ClientConfig{
+		TimeoutMs:           5000,
+		NotLoadCacheAtStart: true,
+		LogDir:              "/tmp/nacos/log",
+		CacheDir:            "/tmp/nacos/cache",
+		RotateTime:          "1h",
+		MaxAge:              3,
+		LogLevel:            "debug",
 	}
 
-	_, err = client.PublishConfig(vo.ConfigParam{DataId: dataID, Group: group, Content: `
-logger:
-  level: debug
-`})
+	client, err := clients.NewConfigClient(
+		vo.NacosClientParam{
+			ClientConfig:  &cc,
+			ServerConfigs: sc,
+		},
+	)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	<-done
+	source := NewConfigSource(client, WithGroup("test"), WithDataID("test.yaml"))
+
+	type fields struct {
+		source config.Source
+	}
+	tests := []struct {
+		name        string
+		fields      fields
+		want        []*config.KeyValue
+		wantErr     bool
+		processFunc func(t *testing.T, w config.Watcher)
+		deferFunc   func(t *testing.T, w config.Watcher)
+	}{
+		{
+			name: "normal",
+			fields: fields{
+				source: source,
+			},
+			wantErr: false,
+			processFunc: func(t *testing.T, w config.Watcher) {
+				_, pErr := client.PublishConfig(vo.ConfigParam{DataId: "test.yaml", Group: "test", Content: "test: test"})
+				if pErr != nil {
+					t.Error(pErr)
+				}
+			},
+			deferFunc: func(t *testing.T, w config.Watcher) {
+				_, dErr := client.DeleteConfig(vo.ConfigParam{DataId: "test.yaml", Group: "test"})
+				if dErr != nil {
+					t.Error(dErr)
+				}
+			},
+			want: []*config.KeyValue{{
+				Key:    "test.yaml",
+				Value:  []byte("test: test"),
+				Format: "yaml",
+			}},
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			s := test.fields.source
+			watch, wErr := s.Watch()
+			if wErr != nil {
+				t.Error(wErr)
+				return
+			}
+			if test.processFunc != nil {
+				test.processFunc(t, watch)
+			}
+			if test.deferFunc != nil {
+				defer test.deferFunc(t, watch)
+			}
+			want, nErr := watch.Next()
+			if (nErr != nil) != test.wantErr {
+				t.Errorf("Watch error = %v, wantErr %v", nErr, test.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(want, test.want) {
+				t.Errorf("Watch watcher = %v, want %v", watch, test.want)
+			}
+		})
+	}
 }
diff --git a/contrib/log/fluent/fluent_test.go b/contrib/log/fluent/fluent_test.go
index 8558f31c..5b7b3e17 100644
--- a/contrib/log/fluent/fluent_test.go
+++ b/contrib/log/fluent/fluent_test.go
@@ -5,6 +5,7 @@ import (
 	"net"
 	"os"
 	"testing"
+	"time"
 
 	"github.com/go-kratos/kratos/v2/log"
 )
@@ -29,6 +30,96 @@ func TestMain(m *testing.M) {
 	os.Exit(m.Run())
 }
 
+func TestWithTimeout(t *testing.T) {
+	opts := new(options)
+	var duration time.Duration = 1000000000
+	funcTimeout := WithTimeout(duration)
+	funcTimeout(opts)
+	if opts.timeout != duration {
+		t.Errorf("WithTimeout() = %v, want %v", opts.timeout, duration)
+	}
+}
+
+func TestWithWriteTimeout(t *testing.T) {
+	opts := new(options)
+	var duration time.Duration = 1000000000
+	funcWriteTimeout := WithWriteTimeout(duration)
+	funcWriteTimeout(opts)
+	if opts.writeTimeout != duration {
+		t.Errorf("WithWriteTimeout() = %v, want %v", opts.writeTimeout, duration)
+	}
+}
+
+func TestWithBufferLimit(t *testing.T) {
+	opts := new(options)
+	bufferLimit := 1000000000
+	funcBufferLimit := WithBufferLimit(bufferLimit)
+	funcBufferLimit(opts)
+	if opts.bufferLimit != bufferLimit {
+		t.Errorf("WithBufferLimit() = %d, want %d", opts.bufferLimit, bufferLimit)
+	}
+}
+
+func TestWithRetryWait(t *testing.T) {
+	opts := new(options)
+	retryWait := 1000000000
+	funcRetryWait := WithRetryWait(retryWait)
+	funcRetryWait(opts)
+	if opts.retryWait != retryWait {
+		t.Errorf("WithRetryWait() = %d, want %d", opts.retryWait, retryWait)
+	}
+}
+
+func TestWithMaxRetry(t *testing.T) {
+	opts := new(options)
+	maxRetry := 1000000000
+	funcMaxRetry := WithMaxRetry(maxRetry)
+	funcMaxRetry(opts)
+	if opts.maxRetry != maxRetry {
+		t.Errorf("WithMaxRetry() = %d, want %d", opts.maxRetry, maxRetry)
+	}
+}
+
+func TestWithMaxRetryWait(t *testing.T) {
+	opts := new(options)
+	maxRetryWait := 1000000000
+	funcMaxRetryWait := WithMaxRetryWait(maxRetryWait)
+	funcMaxRetryWait(opts)
+	if opts.maxRetryWait != maxRetryWait {
+		t.Errorf("WithMaxRetryWait() = %d, want %d", opts.maxRetryWait, maxRetryWait)
+	}
+}
+
+func TestWithTagPrefix(t *testing.T) {
+	opts := new(options)
+	tagPrefix := "tag_prefix"
+	funcTagPrefix := WithTagPrefix(tagPrefix)
+	funcTagPrefix(opts)
+	if opts.tagPrefix != tagPrefix {
+		t.Errorf("WithTagPrefix() = %s, want %s", opts.tagPrefix, tagPrefix)
+	}
+}
+
+func TestWithAsync(t *testing.T) {
+	opts := new(options)
+	async := true
+	funcAsync := WithAsync(async)
+	funcAsync(opts)
+	if opts.async != async {
+		t.Errorf("WithAsync() = %t, want %t", opts.async, async)
+	}
+}
+
+func TestWithForceStopAsyncSend(t *testing.T) {
+	opts := new(options)
+	forceStopAsyncSend := true
+	funcForceStopAsyncSend := WithForceStopAsyncSend(forceStopAsyncSend)
+	funcForceStopAsyncSend(opts)
+	if opts.forceStopAsyncSend != forceStopAsyncSend {
+		t.Errorf("WithForceStopAsyncSend() = %t, want %t", opts.forceStopAsyncSend, forceStopAsyncSend)
+	}
+}
+
 func TestLogger(t *testing.T) {
 	logger, err := NewLogger("tcp://127.0.0.1:24224")
 	if err != nil {
@@ -43,6 +134,38 @@ func TestLogger(t *testing.T) {
 	flog.Error("log", "test")
 }
 
+func TestLoggerWithOpt(t *testing.T) {
+	var duration time.Duration = 1000000000
+	logger, err := NewLogger("tcp://127.0.0.1:24224", WithTimeout(duration))
+	if err != nil {
+		t.Error(err)
+	}
+	defer logger.Close()
+	flog := log.NewHelper(logger)
+
+	flog.Debug("log", "test")
+	flog.Info("log", "test")
+	flog.Warn("log", "test")
+	flog.Error("log", "test")
+}
+
+func TestLoggerError(t *testing.T) {
+	errCase := []string{
+		"foo",
+		"tcp://127.0.0.1/",
+		"tcp://127.0.0.1:1234a",
+		"tcp://127.0.0.1:65535",
+		"https://127.0.0.1:8080",
+		"unix://foo/bar",
+	}
+	for _, errc := range errCase {
+		_, err := NewLogger(errc)
+		if err == nil {
+			t.Error(err)
+		}
+	}
+}
+
 func BenchmarkLoggerPrint(b *testing.B) {
 	b.SetParallelism(100)
 	logger, err := NewLogger("tcp://127.0.0.1:24224")
diff --git a/contrib/registry/consul/registry_test.go b/contrib/registry/consul/registry_test.go
index e6b85e7d..483faf1a 100644
--- a/contrib/registry/consul/registry_test.go
+++ b/contrib/registry/consul/registry_test.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"net"
 	"reflect"
-	"strconv"
 	"testing"
 	"time"
 
@@ -24,7 +23,123 @@ func tcpServer(t *testing.T, lis net.Listener) {
 	}
 }
 
-func TestRegister(t *testing.T) {
+func TestRegistry_Register(t *testing.T) {
+	opts := []Option{
+		WithHealthCheck(false),
+	}
+
+	type args struct {
+		ctx        context.Context
+		serverName string
+		server     []*registry.ServiceInstance
+	}
+
+	test := []struct {
+		name    string
+		args    args
+		want    []*registry.ServiceInstance
+		wantErr bool
+	}{
+		{
+			name: "normal",
+			args: args{
+				ctx:        context.Background(),
+				serverName: "server-1",
+				server: []*registry.ServiceInstance{
+					{
+						ID:        "1",
+						Name:      "server-1",
+						Version:   "v0.0.1",
+						Metadata:  nil,
+						Endpoints: []string{"http://127.0.0.1:8000"},
+					},
+				},
+			},
+			want: []*registry.ServiceInstance{
+				{
+					ID:        "1",
+					Name:      "server-1",
+					Version:   "v0.0.1",
+					Metadata:  nil,
+					Endpoints: []string{"http://127.0.0.1:8000"},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "registry new service replace old service",
+			args: args{
+				ctx:        context.Background(),
+				serverName: "server-1",
+				server: []*registry.ServiceInstance{
+					{
+						ID:        "1",
+						Name:      "server-1",
+						Version:   "v0.0.1",
+						Metadata:  nil,
+						Endpoints: []string{"http://127.0.0.1:8000"},
+					},
+					{
+						ID:        "1",
+						Name:      "server-1",
+						Version:   "v0.0.2",
+						Metadata:  nil,
+						Endpoints: []string{"http://127.0.0.1:8000"},
+					},
+				},
+			},
+			want: []*registry.ServiceInstance{
+				{
+					ID:        "1",
+					Name:      "server-1",
+					Version:   "v0.0.2",
+					Metadata:  nil,
+					Endpoints: []string{"http://127.0.0.1:8000"},
+				},
+			},
+			wantErr: false,
+		},
+	}
+
+	for _, tt := range test {
+		t.Run(tt.name, func(t *testing.T) {
+			cli, err := api.NewClient(&api.Config{Address: "127.0.0.1:8500"})
+			if err != nil {
+				t.Fatalf("create consul client failed: %v", err)
+			}
+
+			r := New(cli, opts...)
+
+			for _, instance := range tt.args.server {
+				err = r.Register(tt.args.ctx, instance)
+				if err != nil {
+					t.Error(err)
+				}
+			}
+
+			watch, err := r.Watch(tt.args.ctx, tt.args.serverName)
+			if err != nil {
+				t.Error(err)
+			}
+			got, err := watch.Next()
+
+			if (err != nil) != tt.wantErr {
+				t.Errorf("GetService() error = %v, wantErr %v", err, tt.wantErr)
+				t.Errorf("GetService() got = %v", got)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("GetService() got = %v, want %v", got, tt.want)
+			}
+
+			for _, instance := range tt.args.server {
+				_ = r.Deregister(tt.args.ctx, instance)
+			}
+		})
+	}
+}
+
+func TestRegistry_GetService(t *testing.T) {
 	addr := fmt.Sprintf("%s:9091", getIntranetIP())
 	lis, err := net.Listen("tcp", addr)
 	if err != nil {
@@ -44,47 +159,212 @@ func TestRegister(t *testing.T) {
 		WithHealthCheckInterval(5),
 	}
 	r := New(cli, opts...)
-	if err != nil {
-		t.Errorf("new consul registry failed: %v", err)
-	}
-	version := strconv.FormatInt(time.Now().Unix(), 10)
-	svc := &registry.ServiceInstance{
-		ID:        "test2233",
-		Name:      "test-provider",
-		Version:   version,
-		Metadata:  map[string]string{"app": "kratos"},
+
+	instance1 := &registry.ServiceInstance{
+		ID:        "1",
+		Name:      "server-1",
+		Version:   "v0.0.1",
 		Endpoints: []string{fmt.Sprintf("tcp://%s?isSecure=false", addr)},
 	}
-	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	defer cancel()
-	err = r.Register(ctx, svc)
-	if err != nil {
-		t.Errorf("Register failed: %v", err)
+
+	type fields struct {
+		registry *Registry
 	}
-	w, err := r.Watch(ctx, "test-provider")
-	if err != nil {
-		t.Errorf("Watchfailed: %v", err)
+	type args struct {
+		ctx         context.Context
+		serviceName string
+	}
+	tests := []struct {
+		name      string
+		fields    fields
+		args      args
+		want      []*registry.ServiceInstance
+		wantErr   bool
+		preFunc   func(t *testing.T)
+		deferFunc func(t *testing.T)
+	}{
+		{
+			name:   "normal",
+			fields: fields{r},
+			args: args{
+				ctx:         context.Background(),
+				serviceName: "server-1",
+			},
+			want:    []*registry.ServiceInstance{instance1},
+			wantErr: false,
+			preFunc: func(t *testing.T) {
+				if err := r.Register(context.Background(), instance1); err != nil {
+					t.Error(err)
+				}
+				watch, err := r.Watch(context.Background(), instance1.Name)
+				if err != nil {
+					t.Error(err)
+				}
+				_, err = watch.Next()
+				if err != nil {
+					t.Error(err)
+				}
+			},
+			deferFunc: func(t *testing.T) {
+				err := r.Deregister(context.Background(), instance1)
+				if err != nil {
+					t.Error(err)
+				}
+			},
+		},
+		{
+			name:   "can't get any",
+			fields: fields{r},
+			args: args{
+				ctx:         context.Background(),
+				serviceName: "server-x",
+			},
+			want:    nil,
+			wantErr: true,
+			preFunc: func(t *testing.T) {
+				if err := r.Register(context.Background(), instance1); err != nil {
+					t.Error(err)
+				}
+				watch, err := r.Watch(context.Background(), instance1.Name)
+				if err != nil {
+					t.Error(err)
+				}
+				_, err = watch.Next()
+				if err != nil {
+					t.Error(err)
+				}
+			},
+			deferFunc: func(t *testing.T) {
+				err := r.Deregister(context.Background(), instance1)
+				if err != nil {
+					t.Error(err)
+				}
+			},
+		},
 	}
 
-	services, err := w.Next()
-	if err != nil {
-		t.Errorf("Next failed: %v", err)
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			if test.preFunc != nil {
+				test.preFunc(t)
+			}
+			if test.deferFunc != nil {
+				defer test.deferFunc(t)
+			}
+
+			service, err := test.fields.registry.GetService(context.Background(), test.args.serviceName)
+			if (err != nil) != test.wantErr {
+				t.Errorf("GetService() error = %v, wantErr %v", err, test.wantErr)
+				t.Errorf("GetService() got = %v", service)
+				return
+			}
+			if !reflect.DeepEqual(service, test.want) {
+				t.Errorf("GetService() got = %v, want %v", service, test.want)
+			}
+		})
 	}
-	if !reflect.DeepEqual(1, len(services)) {
-		t.Errorf("no expect float_key value: %v, but got: %v", len(services), 1)
+}
+
+func TestRegistry_Watch(t *testing.T) {
+	addr := fmt.Sprintf("%s:9091", getIntranetIP())
+
+	time.Sleep(time.Millisecond * 100)
+	cli, err := api.NewClient(&api.Config{Address: "127.0.0.1:8500", WaitTime: 2 * time.Second})
+	if err != nil {
+		t.Fatalf("create consul client failed: %v", err)
 	}
-	if !reflect.DeepEqual("test2233", services[0].ID) {
-		t.Errorf("no expect float_key value: %v, but got: %v", services[0].ID, "test2233")
+
+	instance1 := &registry.ServiceInstance{
+		ID:        "1",
+		Name:      "server-1",
+		Version:   "v0.0.1",
+		Endpoints: []string{fmt.Sprintf("tcp://%s?isSecure=false", addr)},
 	}
-	if !reflect.DeepEqual("test-provider", services[0].Name) {
-		t.Errorf("no expect float_key value: %v, but got: %v", services[0].Name, "test-provider")
+
+	type args struct {
+		ctx      context.Context
+		opts     []Option
+		instance *registry.ServiceInstance
 	}
-	if !reflect.DeepEqual(version, services[0].Version) {
-		t.Errorf("no expect float_key value: %v, but got: %v", services[0].Version, version)
+	tests := []struct {
+		name    string
+		args    args
+		want    []*registry.ServiceInstance
+		wantErr bool
+		preFunc func(t *testing.T)
+	}{
+		{
+			name: "normal",
+			args: args{
+				ctx:      context.Background(),
+				instance: instance1,
+				opts: []Option{
+					WithHealthCheck(false),
+				},
+			},
+			want:    []*registry.ServiceInstance{instance1},
+			wantErr: false,
+			preFunc: func(t *testing.T) {
+			},
+		},
+		{
+			name: "register with healthCheck",
+			args: args{
+				ctx:      context.Background(),
+				instance: instance1,
+				opts: []Option{
+					WithHeartbeat(true),
+					WithHealthCheck(true),
+					WithHealthCheckInterval(5),
+				},
+			},
+			want:    []*registry.ServiceInstance{instance1},
+			wantErr: false,
+			preFunc: func(t *testing.T) {
+				lis, err := net.Listen("tcp", addr)
+				if err != nil {
+					t.Errorf("listen tcp %s failed!", addr)
+				}
+				go tcpServer(t, lis)
+			},
+		},
 	}
-	err = r.Deregister(ctx, svc)
-	if err != nil {
-		t.Errorf("Deregister failed: %v", err)
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if tt.preFunc != nil {
+				tt.preFunc(t)
+			}
+
+			r := New(cli, tt.args.opts...)
+
+			err := r.Register(tt.args.ctx, tt.args.instance)
+			if err != nil {
+				t.Error(err)
+			}
+			defer func() {
+				err = r.Deregister(tt.args.ctx, tt.args.instance)
+				if err != nil {
+					t.Error(err)
+				}
+			}()
+
+			watch, err := r.Watch(tt.args.ctx, tt.args.instance.Name)
+			if err != nil {
+				t.Error(err)
+			}
+
+			service, err := watch.Next()
+
+			if (err != nil) != tt.wantErr {
+				t.Errorf("GetService() error = %v, wantErr %v", err, tt.wantErr)
+				t.Errorf("GetService() got = %v", service)
+				return
+			}
+			if !reflect.DeepEqual(service, tt.want) {
+				t.Errorf("GetService() got = %v, want %v", service, tt.want)
+			}
+		})
 	}
 }
 
diff --git a/contrib/registry/nacos/registry_test.go b/contrib/registry/nacos/registry_test.go
index 62765b0c..1910a68d 100644
--- a/contrib/registry/nacos/registry_test.go
+++ b/contrib/registry/nacos/registry_test.go
@@ -2,8 +2,7 @@ package nacos
 
 import (
 	"context"
-	"log"
-	"net"
+	"reflect"
 	"testing"
 	"time"
 
@@ -14,29 +13,9 @@ import (
 	"github.com/go-kratos/kratos/v2/registry"
 )
 
-func getIntranetIP() string {
-	addrs, err := net.InterfaceAddrs()
-	if err != nil {
-		return "127.0.0.1"
-	}
-
-	for _, address := range addrs {
-		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
-			if ipnet.IP.To4() != nil {
-				return ipnet.IP.String()
-			}
-		}
-	}
-	return "127.0.0.1"
-}
-
-func TestRegistry(t *testing.T) {
-	ip := getIntranetIP()
-	serviceName := "golang-sms@grpc"
-	ctx := context.Background()
-
+func TestRegistry_Register(t *testing.T) {
 	sc := []constant.ServerConfig{
-		*constant.NewServerConfig(ip, 8848),
+		*constant.NewServerConfig("127.0.0.1", 8848),
 	}
 
 	cc := constant.ClientConfig{
@@ -60,76 +39,298 @@ func TestRegistry(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
+	r := New(client)
 
-	_, err = client.RegisterInstance(vo.RegisterInstanceParam{
-		Ip:          "f",
-		Port:        8840,
-		ServiceName: serviceName,
-		Weight:      10,
-		Enable:      true,
-		Healthy:     true,
-		Ephemeral:   true,
-		Metadata:    map[string]string{"idc": "shanghai-xs"},
-	})
-	if err != nil {
-		t.Fatal(err)
+	testServer := &registry.ServiceInstance{
+		ID:        "1",
+		Name:      "test1",
+		Version:   "v1.0.0",
+		Endpoints: []string{"http://127.0.0.1:8080?isSecure=false"},
+	}
+	testServerWithMetadata := &registry.ServiceInstance{
+		ID:        "1",
+		Name:      "test1",
+		Version:   "v1.0.0",
+		Endpoints: []string{"http://127.0.0.1:8080?isSecure=false"},
+		Metadata:  map[string]string{"idc": "shanghai-xs"},
+	}
+	type fields struct {
+		registry *Registry
+	}
+	type args struct {
+		ctx     context.Context
+		service *registry.ServiceInstance
+	}
+	tests := []struct {
+		name      string
+		fields    fields
+		args      args
+		wantErr   bool
+		deferFunc func(t *testing.T)
+	}{
+		{
+			name: "normal",
+			fields: fields{
+				registry: New(client),
+			},
+			args: args{
+				ctx:     context.Background(),
+				service: testServer,
+			},
+			wantErr: false,
+			deferFunc: func(t *testing.T) {
+				err = r.Deregister(context.Background(), testServer)
+				if err != nil {
+					t.Error(err)
+				}
+			},
+		},
+		{
+			name: "withMetadata",
+			fields: fields{
+				registry: New(client),
+			},
+			args: args{
+				ctx:     context.Background(),
+				service: testServerWithMetadata,
+			},
+			wantErr: false,
+			deferFunc: func(t *testing.T) {
+				err = r.Deregister(context.Background(), testServerWithMetadata)
+				if err != nil {
+					t.Error(err)
+				}
+			},
+		},
+		{
+			name: "error",
+			fields: fields{
+				registry: New(client),
+			},
+			args: args{
+				ctx: context.Background(),
+				service: &registry.ServiceInstance{
+					ID:        "1",
+					Name:      "",
+					Version:   "v1.0.0",
+					Endpoints: []string{"http://127.0.0.1:8080?isSecure=false"},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "urlError",
+			fields: fields{
+				registry: New(client),
+			},
+			args: args{
+				ctx: context.Background(),
+				service: &registry.ServiceInstance{
+					ID:        "1",
+					Name:      "test",
+					Version:   "v1.0.0",
+					Endpoints: []string{"127.0.0.1:8080"},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "portError",
+			fields: fields{
+				registry: New(client),
+			},
+			args: args{
+				ctx: context.Background(),
+				service: &registry.ServiceInstance{
+					ID:        "1",
+					Name:      "test",
+					Version:   "v1.0.0",
+					Endpoints: []string{"http://127.0.0.1888"},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "withCluster",
+			fields: fields{
+				registry: New(client, WithCluster("test")),
+			},
+			args: args{
+				ctx:     context.Background(),
+				service: testServer,
+			},
+			wantErr: false,
+		},
+		{
+			name: "withGroup",
+			fields: fields{
+				registry: New(client, WithGroup("TEST_GROUP")),
+			},
+			args: args{
+				ctx:     context.Background(),
+				service: testServer,
+			},
+			wantErr: false,
+		},
+		{
+			name: "withWeight",
+			fields: fields{
+				registry: New(client, WithWeight(200)),
+			},
+			args: args{
+				ctx:     context.Background(),
+				service: testServer,
+			},
+			wantErr: false,
+		},
+		{
+			name: "withPrefix",
+			fields: fields{
+				registry: New(client, WithPrefix("test")),
+			},
+			args: args{
+				ctx:     context.Background(),
+				service: testServer,
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			r := tt.fields.registry
+			if err := r.Register(tt.args.ctx, tt.args.service); (err != nil) != tt.wantErr {
+				t.Errorf("Register error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
 	}
+}
 
-	time.Sleep(time.Second)
+func TestRegistry_Deregister(t *testing.T) {
+	testServer := &registry.ServiceInstance{
+		ID:        "1",
+		Name:      "test2",
+		Version:   "v1.0.0",
+		Endpoints: []string{"http://127.0.0.1:8080?isSecure=false"},
+	}
 
-	is, err := client.GetService(vo.GetServiceParam{
-		ServiceName: serviceName,
-	})
-	if err != nil {
-		t.Fatal(err)
+	type args struct {
+		ctx     context.Context
+		service *registry.ServiceInstance
 	}
-	t.Logf("is %#v", is)
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+		preFunc func(t *testing.T)
+	}{
+		{
+			name: "normal",
+			args: args{
+				ctx:     context.Background(),
+				service: testServer,
+			},
+			wantErr: false,
+			preFunc: func(t *testing.T) {
+				sc := []constant.ServerConfig{
+					*constant.NewServerConfig("127.0.0.1", 8848),
+				}
 
-	time.Sleep(time.Second)
-	r := New(client)
+				cc := constant.ClientConfig{
+					NamespaceId:         "public", // namespace id
+					TimeoutMs:           5000,
+					NotLoadCacheAtStart: true,
+					LogDir:              "/tmp/nacos/log",
+					CacheDir:            "/tmp/nacos/cache",
+					RotateTime:          "1h",
+					MaxAge:              3,
+					LogLevel:            "debug",
+				}
 
-	go func() {
-		var (
-			w        registry.Watcher
-			watchErr error
-		)
-		w, watchErr = r.Watch(ctx, "golang-sms@grpc")
-		if watchErr != nil {
-			log.Fatal(watchErr)
-		}
-		for {
-			var res []*registry.ServiceInstance
-			res, watchErr = w.Next()
-			if watchErr != nil {
-				return
-			}
-			log.Printf("watch: %d", len(res))
-			for _, r := range res {
-				log.Printf("next: %+v", r)
+				// a more graceful way to create naming client
+				client, err := clients.NewNamingClient(
+					vo.NacosClientParam{
+						ClientConfig:  &cc,
+						ServerConfigs: sc,
+					},
+				)
+				if err != nil {
+					t.Fatal(err)
+				}
+				r := New(client)
+				err = r.Register(context.Background(), testServer)
+				if err != nil {
+					t.Error(err)
+				}
+			},
+		},
+		{
+			name: "error",
+			args: args{
+				ctx: context.Background(),
+				service: &registry.ServiceInstance{
+					ID:        "1",
+					Name:      "test",
+					Version:   "v1.0.0",
+					Endpoints: []string{"127.0.0.1:8080"},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "errorPort",
+			args: args{
+				ctx: context.Background(),
+				service: &registry.ServiceInstance{
+					ID:        "1",
+					Name:      "notExist",
+					Version:   "v1.0.0",
+					Endpoints: []string{"http://127.0.0.18080"},
+				},
+			},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			sc := []constant.ServerConfig{
+				*constant.NewServerConfig("127.0.0.1", 8848),
 			}
-		}
-	}()
 
-	time.Sleep(time.Second)
+			cc := constant.ClientConfig{
+				NamespaceId:         "public", // namespace id
+				TimeoutMs:           5000,
+				NotLoadCacheAtStart: true,
+				LogDir:              "/tmp/nacos/log",
+				CacheDir:            "/tmp/nacos/cache",
+				RotateTime:          "1h",
+				MaxAge:              3,
+				LogLevel:            "debug",
+			}
 
-	ins, err := r.GetService(ctx, serviceName)
-	if err != nil {
-		t.Fatal(err)
-	}
-	for _, in := range ins {
-		t.Logf("ins: %#v", in)
+			// a more graceful way to create naming client
+			client, err := clients.NewNamingClient(
+				vo.NacosClientParam{
+					ClientConfig:  &cc,
+					ServerConfigs: sc,
+				},
+			)
+			if err != nil {
+				t.Fatal(err)
+			}
+			r := New(client)
+			if tt.preFunc != nil {
+				tt.preFunc(t)
+			}
+			if err := r.Deregister(tt.args.ctx, tt.args.service); (err != nil) != tt.wantErr {
+				t.Errorf("Deregister error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
 	}
-
-	time.Sleep(time.Second)
 }
 
-func TestRegistryMany(t *testing.T) {
-	ip := getIntranetIP()
-	serviceName := "golang-sms@grpc"
-	// ctx := context.Background()
-
+func TestRegistry_GetService(t *testing.T) {
 	sc := []constant.ServerConfig{
-		*constant.NewServerConfig(ip, 8848),
+		*constant.NewServerConfig("127.0.0.1", 8848),
 	}
 
 	cc := constant.ClientConfig{
@@ -153,61 +354,219 @@ func TestRegistryMany(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-
-	_, err = client.RegisterInstance(vo.RegisterInstanceParam{
-		Ip:          "f1",
-		Port:        8840,
-		ServiceName: serviceName,
-		Weight:      10,
-		Enable:      true,
-		Healthy:     true,
-		Ephemeral:   true,
-		Metadata:    map[string]string{"idc": "shanghai-xs"},
-	})
-	if err != nil {
-		t.Fatal(err)
+	r := New(client)
+	testServer := &registry.ServiceInstance{
+		ID:        "1",
+		Name:      "test3",
+		Version:   "v1.0.0",
+		Endpoints: []string{"grpc://127.0.0.1:8080?isSecure=false"},
 	}
 
-	_, err = client.RegisterInstance(vo.RegisterInstanceParam{
-		Ip:          "f2",
-		Port:        8840,
-		ServiceName: serviceName,
-		Weight:      10,
-		Enable:      true,
-		Healthy:     true,
-		Ephemeral:   true,
-		Metadata:    map[string]string{"idc": "shanghai-xs"},
-	})
-	if err != nil {
-		t.Fatal(err)
+	type fields struct {
+		registry *Registry
 	}
+	type args struct {
+		ctx         context.Context
+		serviceName string
+	}
+	tests := []struct {
+		name      string
+		fields    fields
+		args      args
+		want      []*registry.ServiceInstance
+		wantErr   bool
+		preFunc   func(t *testing.T)
+		deferFunc func(t *testing.T)
+	}{
+		{
+			name: "normal",
+			preFunc: func(t *testing.T) {
+				err = r.Register(context.Background(), testServer)
+				if err != nil {
+					t.Error(err)
+				}
+				time.Sleep(time.Second)
+			},
+			deferFunc: func(t *testing.T) {
+				err = r.Deregister(context.Background(), testServer)
+				if err != nil {
+					t.Error(err)
+				}
+			},
+			fields: fields{
+				registry: r,
+			},
+			args: args{
+				ctx:         context.Background(),
+				serviceName: testServer.Name + "." + "grpc",
+			},
+			want: []*registry.ServiceInstance{{
+				ID:        "127.0.0.1#8080#DEFAULT#DEFAULT_GROUP@@test3.grpc",
+				Name:      "DEFAULT_GROUP@@test3.grpc",
+				Version:   "v1.0.0",
+				Metadata:  map[string]string{"version": "v1.0.0", "kind": "grpc"},
+				Endpoints: []string{"grpc://127.0.0.1:8080"},
+			}},
+			wantErr: false,
+		},
+		{
+			name: "errorNotExist",
+			fields: fields{
+				registry: r,
+			},
+			args: args{
+				ctx:         context.Background(),
+				serviceName: "notExist",
+			},
+			want:    nil,
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if tt.preFunc != nil {
+				tt.preFunc(t)
+			}
+			if tt.deferFunc != nil {
+				defer tt.deferFunc(t)
+			}
+			r := tt.fields.registry
+			got, err := r.GetService(tt.args.ctx, tt.args.serviceName)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("GetService error = %v, wantErr %v", err, tt.wantErr)
+				t.Errorf("GetService got = %v", got)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("GetService got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
 
-	_, err = client.RegisterInstance(vo.RegisterInstanceParam{
-		Ip:          "f3",
-		Port:        8840,
-		ServiceName: serviceName,
-		Weight:      10,
-		Enable:      true,
-		Healthy:     true,
-		Ephemeral:   true,
-		Metadata:    map[string]string{"idc": "shanghai-xs"},
-	})
-	if err != nil {
-		t.Fatal(err)
+func TestRegistry_Watch(t *testing.T) {
+	sc := []constant.ServerConfig{
+		*constant.NewServerConfig("127.0.0.1", 8848),
 	}
 
-	time.Sleep(time.Second)
+	cc := constant.ClientConfig{
+		NamespaceId:         "public", // namespace id
+		TimeoutMs:           5000,
+		NotLoadCacheAtStart: true,
+		LogDir:              "/tmp/nacos/log",
+		CacheDir:            "/tmp/nacos/cache",
+		RotateTime:          "1h",
+		MaxAge:              3,
+		LogLevel:            "debug",
+	}
 
-	is, err := client.GetService(vo.GetServiceParam{
-		ServiceName: serviceName,
-	})
+	// a more graceful way to create naming client
+	client, err := clients.NewNamingClient(
+		vo.NacosClientParam{
+			ClientConfig:  &cc,
+			ServerConfigs: sc,
+		},
+	)
 	if err != nil {
 		t.Fatal(err)
 	}
+	r := New(client)
 
-	for _, host := range is.Hosts {
-		t.Logf("host: %#v,e: %v", host, err)
+	testServer := &registry.ServiceInstance{
+		ID:        "1",
+		Name:      "test4",
+		Version:   "v1.0.0",
+		Endpoints: []string{"grpc://127.0.0.1:8080?isSecure=false"},
 	}
 
-	time.Sleep(time.Second)
+	cancelCtx, cancel := context.WithCancel(context.Background())
+	type fields struct {
+		registry *Registry
+	}
+	type args struct {
+		ctx         context.Context
+		serviceName string
+	}
+	tests := []struct {
+		name        string
+		fields      fields
+		args        args
+		wantErr     bool
+		want        []*registry.ServiceInstance
+		processFunc func(t *testing.T)
+	}{
+		{
+			name: "normal",
+			fields: fields{
+				registry: New(client),
+			},
+			args: args{
+				ctx:         context.Background(),
+				serviceName: testServer.Name + "." + "grpc",
+			},
+			wantErr: false,
+			want: []*registry.ServiceInstance{{
+				ID:        "127.0.0.1#8080#DEFAULT#DEFAULT_GROUP@@test4.grpc",
+				Name:      "DEFAULT_GROUP@@test4.grpc",
+				Version:   "v1.0.0",
+				Metadata:  map[string]string{"version": "v1.0.0", "kind": "grpc"},
+				Endpoints: []string{"grpc://127.0.0.1:8080"},
+			}},
+			processFunc: func(t *testing.T) {
+				err = r.Register(context.Background(), testServer)
+				if err != nil {
+					t.Error(err)
+				}
+			},
+		},
+		{
+			name: "ctxCancel",
+			fields: fields{
+				registry: r,
+			},
+			args: args{
+				ctx:         cancelCtx,
+				serviceName: testServer.Name,
+			},
+			wantErr: true,
+			want:    nil,
+			processFunc: func(t *testing.T) {
+				cancel()
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			r := tt.fields.registry
+			watch, err := r.Watch(tt.args.ctx, tt.args.serviceName)
+			if err != nil {
+				t.Error(err)
+				return
+			}
+			defer func() {
+				err = watch.Stop()
+				if err != nil {
+					t.Error(err)
+				}
+			}()
+			_, err = watch.Next()
+			if err != nil {
+				t.Error(err)
+				return
+			}
+
+			if tt.processFunc != nil {
+				tt.processFunc(t)
+			}
+
+			want, err := watch.Next()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("Watch error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(want, tt.want) {
+				t.Errorf("Watch watcher = %v, want %v", watch, tt.want)
+			}
+		})
+	}
 }