diff --git a/binary_test.go b/binary_test.go index 4809ce6..3929b7a 100644 --- a/binary_test.go +++ b/binary_test.go @@ -1,12 +1,12 @@ package render_test import ( + "encoding" "errors" "testing" "github.com/jimeh/go-render" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) type mockBinaryMarshaler struct { @@ -14,6 +14,8 @@ type mockBinaryMarshaler struct { err error } +var _ encoding.BinaryMarshaler = (*mockBinaryMarshaler)(nil) + func (mbm *mockBinaryMarshaler) MarshalBinary() ([]byte, error) { return mbm.data, mbm.err } @@ -57,16 +59,12 @@ func TestBinary_Render(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := &render.Binary{} - - var err error - var got string w := &mockWriter{WriteErr: tt.writeErr} - err = b.Render(w, tt.value) - got = w.String() + err := b.Render(w, tt.value) + got := w.String() if tt.wantErr != "" { - require.Error(t, err) assert.EqualError(t, err, tt.wantErr) } for _, e := range tt.wantErrIs { @@ -74,7 +72,7 @@ func TestBinary_Render(t *testing.T) { } if tt.wantErr == "" && len(tt.wantErrIs) == 0 { - require.NoError(t, err) + assert.NoError(t, err) assert.Equal(t, tt.want, got) } }) diff --git a/format.go b/format.go index febe1f8..96efc19 100644 --- a/format.go +++ b/format.go @@ -1,17 +1,22 @@ package render import ( + "fmt" "io" ) +var ErrUnsupportedFormat = fmt.Errorf("%w: unsupported format", Err) + // FormatRenderer is a renderer that delegates rendering to another renderer // based on a format value. type FormatRenderer struct { + // Renderers is a map of format names to renderers. When Render is called, + // the format is used to look up the renderer to use. Renderers map[string]Renderer } // Render renders a value to an io.Writer using the specified format. If the -// format is not supported, ErrCannotRender is returned. +// format is not supported, ErrUnsupportedFormat is returned. // // If the format is supported, but the value cannot be rendered to the format, // the error returned by the renderer is returned. In most cases this will be @@ -23,5 +28,5 @@ func (r *FormatRenderer) Render(w io.Writer, format string, v any) error { return renderer.Render(w, v) } - return ErrCannotRender + return ErrUnsupportedFormat } diff --git a/format_test.go b/format_test.go index 4ceaea0..de94946 100644 --- a/format_test.go +++ b/format_test.go @@ -7,7 +7,6 @@ import ( "github.com/jimeh/go-render" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestFormatRenderer_Render(t *testing.T) { @@ -46,7 +45,7 @@ func TestFormatRenderer_Render(t *testing.T) { renderers: map[string]render.Renderer{}, format: "unknown", value: struct{}{}, - wantErrIs: []error{render.Err, render.ErrCannotRender}, + wantErrIs: []error{render.Err, render.ErrUnsupportedFormat}, }, } for _, tt := range tests { @@ -54,13 +53,12 @@ func TestFormatRenderer_Render(t *testing.T) { fr := &render.FormatRenderer{ Renderers: tt.renderers, } - var buf bytes.Buffer + err := fr.Render(&buf, tt.format, tt.value) got := buf.String() if tt.wantErr != "" { - require.Error(t, err) assert.EqualError(t, err, tt.wantErr) } for _, e := range tt.wantErrIs { @@ -68,7 +66,7 @@ func TestFormatRenderer_Render(t *testing.T) { } if tt.wantErr == "" && len(tt.wantErrIs) == 0 { - require.NoError(t, err) + assert.NoError(t, err) assert.Equal(t, tt.want, got) } }) diff --git a/json_test.go b/json_test.go index 68af345..bd78e0a 100644 --- a/json_test.go +++ b/json_test.go @@ -2,6 +2,8 @@ package render_test import ( "bytes" + "encoding/json" + "errors" "testing" "github.com/jimeh/go-render" @@ -9,6 +11,17 @@ import ( "github.com/stretchr/testify/require" ) +type mockJSONMarshaler struct { + data []byte + err error +} + +var _ json.Marshaler = (*mockJSONMarshaler)(nil) + +func (mjm *mockJSONMarshaler) MarshalJSON() ([]byte, error) { + return mjm.data, mjm.err +} + func TestJSON_Render(t *testing.T) { tests := []struct { name string @@ -29,18 +42,35 @@ func TestJSON_Render(t *testing.T) { { name: "simple object with pretty", pretty: true, - indent: " ", value: map[string]int{"age": 30}, want: "{\n \"age\": 30\n}\n", }, { - name: "with prefix and indent", + name: "pretty with prefix and indent", pretty: true, prefix: "// ", indent: "\t", value: map[string]int{"age": 30}, want: "{\n// \t\"age\": 30\n// }\n", }, + { + name: "prefix and indent without pretty", + pretty: false, + prefix: "// ", + indent: "\t", + value: map[string]int{"age": 30}, + want: "{\"age\":30}\n", + }, + { + name: "implements json.Marshaler", + value: &mockJSONMarshaler{data: []byte(`{"age":30}`)}, + want: "{\"age\":30}\n", + }, + { + name: "error from json.Marshaler", + value: &mockJSONMarshaler{err: errors.New("marshal error!!1")}, + wantErrIs: []error{render.Err}, + }, { name: "invalid value", pretty: false, @@ -49,7 +79,6 @@ func TestJSON_Render(t *testing.T) { wantErrIs: []error{render.Err}, }, } - for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { j := &render.JSON{ @@ -57,9 +86,10 @@ func TestJSON_Render(t *testing.T) { Prefix: tt.prefix, Indent: tt.indent, } - var buf bytes.Buffer + err := j.Render(&buf, tt.value) + got := buf.String() if tt.wantErr != "" { require.Error(t, err) @@ -71,7 +101,6 @@ func TestJSON_Render(t *testing.T) { if tt.wantErr == "" && len(tt.wantErrIs) == 0 { require.NoError(t, err) - got := buf.String() assert.Equal(t, tt.want, got) } }) diff --git a/multi_test.go b/multi_test.go index 6774d9f..5907802 100644 --- a/multi_test.go +++ b/multi_test.go @@ -7,7 +7,6 @@ import ( "github.com/jimeh/go-render" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestMultiRenderer_Render(t *testing.T) { @@ -88,13 +87,12 @@ func TestMultiRenderer_Render(t *testing.T) { mr := &render.MultiRenderer{ Renderers: tt.renderers, } - var buf bytes.Buffer + err := mr.Render(&buf, tt.value) got := buf.String() if tt.wantErr != "" { - require.Error(t, err) assert.EqualError(t, err, tt.wantErr) } for _, e := range tt.wantErrIs { @@ -102,7 +100,7 @@ func TestMultiRenderer_Render(t *testing.T) { } if tt.wantErr == "" && len(tt.wantErrIs) == 0 { - require.NoError(t, err) + assert.NoError(t, err) assert.Equal(t, tt.want, got) } }) diff --git a/render.go b/render.go index c0ee7fc..42039d0 100644 --- a/render.go +++ b/render.go @@ -48,21 +48,22 @@ var ( Renderers: []Renderer{DefaultStringer, DefaultWriterTo}, } - // DefaultBinaryMarshaler is the default binary marshaler renderer. It + // DefaultBinary is the default binary marshaler renderer. It // renders values using the encoding.BinaryMarshaler interface. - DefaultBinaryMarshaler = &Binary{} + DefaultBinary = &Binary{} // DefaultRenderer is the default renderer, used by the package level Render - // function. It supports the "json", "xml", "yaml", "text", "binary" - // formats. + // function. DefaultRenderer = &FormatRenderer{map[string]Renderer{ + "bin": DefaultBinary, + "binary": DefaultBinary, "json": DefaultJSON, + "plain": DefaultText, + "text": DefaultText, + "txt": DefaultText, "xml": DefaultXML, "yaml": DefaultYAML, "yml": DefaultYAML, - "text": DefaultText, - "binary": DefaultBinaryMarshaler, - "bin": DefaultBinaryMarshaler, }} ) @@ -81,10 +82,13 @@ var ( // - "text": Renders values using the fmt.Stringer and io.WriterTo interfaces. // This means a value must implement either the fmt.Stringer or io.WriterTo // interfaces to be rendered. +// - "txt": Alias for "text". +// - "plain": Alias for "text". // - "binary": Renders values using the encoding.BinaryMarshaler interface. // - "bin": Alias for "binary". // -// If the format is not supported, a ErrCannotRender error will be returned. +// If the format is not supported, a ErrUnsupportedFormat error will be +// returned. func Render(w io.Writer, format string, v any) error { return DefaultRenderer.Render(w, format, v) } diff --git a/render_test.go b/render_test.go index c9991bf..c5f0f9c 100644 --- a/render_test.go +++ b/render_test.go @@ -2,7 +2,13 @@ package render_test import ( "bytes" + "encoding/xml" + "errors" "io" + "testing" + + "github.com/jimeh/go-render" + "github.com/stretchr/testify/assert" ) type mockWriter struct { @@ -10,6 +16,8 @@ type mockWriter struct { buf bytes.Buffer } +var _ io.Writer = (*mockWriter)(nil) + func (mw *mockWriter) Write(p []byte) (n int, err error) { if mw.WriteErr != nil { return 0, mw.WriteErr @@ -27,6 +35,8 @@ type mockRenderer struct { err error } +var _ render.Renderer = (*mockRenderer)(nil) + func (m *mockRenderer) Render(w io.Writer, _ any) error { _, err := w.Write([]byte(m.output)) @@ -36,3 +46,478 @@ func (m *mockRenderer) Render(w io.Writer, _ any) error { return err } + +func TestDefaultJSON(t *testing.T) { + assert.Equal(t, &render.JSON{Pretty: true}, render.DefaultJSON) +} + +func TestDefaultXML(t *testing.T) { + assert.Equal(t, &render.XML{Pretty: true}, render.DefaultXML) +} + +func TestDefaultYAML(t *testing.T) { + assert.Equal(t, &render.YAML{Indent: 2}, render.DefaultYAML) +} + +func TestDefaultWriterTo(t *testing.T) { + assert.Equal(t, &render.WriterTo{}, render.DefaultWriterTo) +} + +func TestDefaultStringer(t *testing.T) { + assert.Equal(t, &render.Stringer{}, render.DefaultStringer) +} + +func TestDefaultText(t *testing.T) { + want := &render.MultiRenderer{ + Renderers: []render.Renderer{ + &render.Stringer{}, + &render.WriterTo{}, + }, + } + + assert.Equal(t, want, render.DefaultText) +} + +func TestDefaultBinary(t *testing.T) { + assert.Equal(t, &render.Binary{}, render.DefaultBinary) +} + +func TestDefaultRenderer(t *testing.T) { + want := &render.FormatRenderer{ + Renderers: map[string]render.Renderer{ + "bin": render.DefaultBinary, + "binary": render.DefaultBinary, + "json": render.DefaultJSON, + "plain": render.DefaultText, + "text": render.DefaultText, + "txt": render.DefaultText, + "xml": render.DefaultXML, + "yaml": render.DefaultYAML, + "yml": render.DefaultYAML, + }, + } + + assert.Equal(t, want, render.DefaultRenderer) +} + +func TestRender(t *testing.T) { + tests := []struct { + name string + writeErr error + format string + value any + want string + wantErr string + wantErrIs []error + wantPanic string + }{ + // "bin" format. + { + name: "bin format with binary marshaler", + format: "bin", + value: &mockBinaryMarshaler{data: []byte("test string")}, + want: "test string", + }, + { + name: "bin format without binary marshaler", + format: "bin", + value: struct{}{}, + wantErrIs: []error{render.Err, render.ErrCannotRender}, + }, + { + name: "bin format with error marshaling", + format: "bin", + value: &mockBinaryMarshaler{ + data: []byte("test string"), + err: errors.New("marshal error!!1"), + }, + wantErr: "render: marshal error!!1", + wantErrIs: []error{render.Err}, + }, + { + name: "bin format with error writing to writer", + format: "bin", + writeErr: errors.New("write error!!1"), + value: &mockBinaryMarshaler{data: []byte("test string")}, + wantErr: "render: write error!!1", + wantErrIs: []error{render.Err}, + }, + { + name: "bin format with invalid type", + format: "bin", + value: make(chan int), + wantErr: "render: cannot render", + wantErrIs: []error{render.Err, render.ErrCannotRender}, + }, + // "binary" format. + { + name: "binary format with binary marshaler", + format: "binary", + value: &mockBinaryMarshaler{data: []byte("test string")}, + want: "test string", + }, + { + name: "binary format without binary marshaler", + format: "binary", + value: struct{}{}, + wantErrIs: []error{render.Err, render.ErrCannotRender}, + }, + { + name: "binary format with error marshaling", + format: "binary", + value: &mockBinaryMarshaler{ + data: []byte("test string"), + err: errors.New("marshal error!!1"), + }, + wantErr: "render: marshal error!!1", + wantErrIs: []error{render.Err}, + }, + { + name: "binary format with error writing to writer", + format: "binary", + writeErr: errors.New("write error!!1"), + value: &mockBinaryMarshaler{data: []byte("test string")}, + wantErr: "render: write error!!1", + wantErrIs: []error{render.Err}, + }, + { + name: "binary format with invalid type", + format: "binary", + value: make(chan int), + wantErr: "render: cannot render", + wantErrIs: []error{render.Err, render.ErrCannotRender}, + }, + // "json" format. + { + name: "json format", + format: "json", + value: map[string]int{"age": 30}, + want: "{\n \"age\": 30\n}\n", + }, + { + name: "json format with json marshaler", + format: "json", + value: &mockJSONMarshaler{data: []byte(`{"age":30}`)}, + want: "{\n \"age\": 30\n}\n", + }, + { + name: "json format with error from json marshaler", + format: "json", + value: &mockJSONMarshaler{err: errors.New("marshal error!!1")}, + wantErrIs: []error{render.Err}, + }, + { + name: "json format with error writing to writer", + format: "json", + writeErr: errors.New("write error!!1"), + value: map[string]int{"age": 30}, + wantErr: "render: write error!!1", + wantErrIs: []error{render.Err}, + }, + { + name: "json format with invalid type", + format: "json", + value: make(chan int), + wantErr: "render: json: unsupported type: chan int", + wantErrIs: []error{render.Err}, + }, + // "plain" format. + { + name: "plain format with fmt.Stringer", + format: "plain", + value: &mockStringer{value: "test string"}, + want: "test string", + }, + { + name: "plain format with io.WriterTo", + format: "plain", + value: &mockWriterTo{value: "test string"}, + want: "test string", + }, + { + name: "plain format without fmt.Stringer or io.WriterTo", + format: "plain", + value: struct{}{}, + wantErrIs: []error{render.Err, render.ErrCannotRender}, + }, + { + name: "plain format with error writing to writer", + format: "plain", + writeErr: errors.New("write error!!1"), + value: &mockStringer{value: "test string"}, + wantErr: "render: write error!!1", + wantErrIs: []error{render.Err}, + }, + { + name: "plain format with error from io.WriterTo", + format: "plain", + value: &mockWriterTo{ + value: "test string", + err: errors.New("WriteTo error!!1"), + }, + wantErr: "render: WriteTo error!!1", + wantErrIs: []error{render.Err}, + }, + { + name: "plain format with invalid type", + format: "plain", + value: make(chan int), + wantErr: "render: cannot render", + wantErrIs: []error{render.Err, render.ErrCannotRender}, + }, + // "text" format. + { + name: "text format with fmt.Stringer", + format: "text", + value: &mockStringer{value: "test string"}, + want: "test string", + }, + { + name: "text format with io.WriterTo", + format: "text", + value: &mockWriterTo{value: "test string"}, + want: "test string", + }, + { + name: "text format without fmt.Stringer or io.WriterTo", + format: "text", + value: struct{}{}, + wantErrIs: []error{render.Err, render.ErrCannotRender}, + }, + { + name: "text format with error writing to writer", + format: "text", + writeErr: errors.New("write error!!1"), + value: &mockStringer{value: "test string"}, + wantErr: "render: write error!!1", + wantErrIs: []error{render.Err}, + }, + { + name: "text format with error from io.WriterTo", + format: "text", + value: &mockWriterTo{ + value: "test string", + err: errors.New("WriteTo error!!1"), + }, + wantErr: "render: WriteTo error!!1", + wantErrIs: []error{render.Err}, + }, + { + name: "text format with invalid type", + format: "text", + value: make(chan int), + wantErr: "render: cannot render", + wantErrIs: []error{render.Err, render.ErrCannotRender}, + }, + // "txt" format. + { + name: "txt format with fmt.Stringer", + format: "txt", + value: &mockStringer{value: "test string"}, + want: "test string", + }, + { + name: "txt format with io.WriterTo", + format: "txt", + value: &mockWriterTo{value: "test string"}, + want: "test string", + }, + { + name: "txt format without fmt.Stringer or io.WriterTo", + format: "txt", + value: struct{}{}, + wantErrIs: []error{render.Err, render.ErrCannotRender}, + }, + { + name: "txt format with error writing to writer", + format: "txt", + writeErr: errors.New("write error!!1"), + value: &mockStringer{value: "test string"}, + wantErr: "render: write error!!1", + wantErrIs: []error{render.Err}, + }, + { + name: "txt format with error from io.WriterTo", + format: "txt", + value: &mockWriterTo{ + value: "test string", + err: errors.New("WriteTo error!!1"), + }, + wantErr: "render: WriteTo error!!1", + wantErrIs: []error{render.Err}, + }, + { + name: "txt format with invalid type", + format: "txt", + value: make(chan int), + wantErr: "render: cannot render", + wantErrIs: []error{render.Err, render.ErrCannotRender}, + }, + // "xml" format. + { + name: "xml format", + format: "xml", + value: struct { + XMLName xml.Name `xml:"user"` + Age int `xml:"age"` + }{Age: 30}, + want: "\n 30\n", + }, + { + name: "xml format with xml.Marshaler", + format: "xml", + value: &mockXMLMarshaler{elm: "test string"}, + want: "test string", + }, + { + name: "xml format with error from xml.Marshaler", + format: "xml", + value: &mockXMLMarshaler{err: errors.New("marshal error!!1")}, + wantErr: "render: marshal error!!1", + wantErrIs: []error{render.Err}, + }, + { + name: "xml format with error writing to writer", + format: "xml", + writeErr: errors.New("write error!!1"), + value: struct { + XMLName xml.Name `xml:"user"` + Age int `xml:"age"` + }{Age: 30}, + wantErr: "render: write error!!1", + wantErrIs: []error{render.Err}, + }, + { + name: "xml format with invalid value", + format: "xml", + value: make(chan int), + wantErr: "render: xml: unsupported type: chan int", + wantErrIs: []error{render.Err}, + }, + // "yaml" format. + { + name: "yaml format", + format: "yaml", + value: map[string]int{"age": 30}, + want: "age: 30\n", + }, + { + name: "yaml format with nested structure", + format: "yaml", + value: map[string]any{ + "user": map[string]any{ + "age": 30, + "name": "John Doe", + }, + }, + want: "user:\n age: 30\n name: John Doe\n", + }, + { + name: "yaml format with yaml.Marshaler", + format: "yaml", + value: &mockYAMLMarshaler{val: map[string]int{"age": 30}}, + want: "age: 30\n", + }, + { + name: "yaml format with error from yaml.Marshaler", + format: "yaml", + value: &mockYAMLMarshaler{err: errors.New("mock error")}, + wantErr: "render: mock error", + wantErrIs: []error{render.Err}, + }, + { + name: "yaml format with error writing to writer", + format: "yaml", + writeErr: errors.New("write error!!1"), + value: map[string]int{"age": 30}, + wantErr: "render: yaml: write error: write error!!1", + wantErrIs: []error{render.Err}, + }, + { + name: "yaml format with invalid type", + format: "yaml", + value: make(chan int), + wantPanic: "cannot marshal type: chan int", + }, + // "yml" format. + { + name: "yml format", + format: "yml", + value: map[string]int{"age": 30}, + want: "age: 30\n", + }, + { + name: "yml format with nested structure", + format: "yml", + value: map[string]any{ + "user": map[string]any{ + "age": 30, + "name": "John Doe", + }, + }, + want: "user:\n age: 30\n name: John Doe\n", + }, + { + name: "yml format with yaml.Marshaler", + format: "yml", + value: &mockYAMLMarshaler{val: map[string]int{"age": 30}}, + want: "age: 30\n", + }, + { + name: "yml format with error from yaml.Marshaler", + format: "yml", + value: &mockYAMLMarshaler{err: errors.New("mock error")}, + wantErr: "render: mock error", + wantErrIs: []error{render.Err}, + }, + { + name: "yml format with error writing to writer", + format: "yml", + writeErr: errors.New("write error!!1"), + value: map[string]int{"age": 30}, + wantErr: "render: yaml: write error: write error!!1", + wantErrIs: []error{render.Err}, + }, + { + name: "yml format with invalid type", + format: "yml", + value: make(chan int), + wantPanic: "cannot marshal type: chan int", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := &mockWriter{WriteErr: tt.writeErr} + + var err error + var panicRes any + func() { + defer func() { + if r := recover(); r != nil { + panicRes = r + } + }() + err = render.Render(w, tt.format, tt.value) + }() + + got := w.String() + + if tt.wantPanic != "" { + assert.Equal(t, tt.wantPanic, panicRes) + } + + if tt.wantErr != "" { + assert.EqualError(t, err, tt.wantErr) + } + for _, e := range tt.wantErrIs { + assert.ErrorIs(t, err, e) + } + + if tt.wantPanic == "" && + tt.wantErr == "" && len(tt.wantErrIs) == 0 { + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + } + }) + } +} diff --git a/stringer.go b/stringer.go index f76b21e..51ee7e0 100644 --- a/stringer.go +++ b/stringer.go @@ -19,7 +19,7 @@ func (s *Stringer) Render(w io.Writer, v any) error { return ErrCannotRender } - _, err := fmt.Fprint(w, x.String()) + _, err := w.Write([]byte(x.String())) if err != nil { return fmt.Errorf("%w: %w", Err, err) } diff --git a/stringer_test.go b/stringer_test.go index 2096b39..b328e71 100644 --- a/stringer_test.go +++ b/stringer_test.go @@ -2,17 +2,19 @@ package render_test import ( "errors" + "fmt" "testing" "github.com/jimeh/go-render" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) type mockStringer struct { value string } +var _ fmt.Stringer = (*mockStringer)(nil) + func (ms *mockStringer) String() string { return ms.value } @@ -47,16 +49,12 @@ func TestStringer_Render(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := &render.Stringer{} - - var err error - var got string w := &mockWriter{WriteErr: tt.writeErr} - err = s.Render(w, tt.value) - got = w.String() + err := s.Render(w, tt.value) + got := w.String() if tt.wantErr != "" { - require.Error(t, err) assert.EqualError(t, err, tt.wantErr) } for _, e := range tt.wantErrIs { @@ -64,7 +62,7 @@ func TestStringer_Render(t *testing.T) { } if tt.wantErr == "" && len(tt.wantErrIs) == 0 { - require.NoError(t, err) + assert.NoError(t, err) assert.Equal(t, tt.want, got) } }) diff --git a/writer_to_test.go b/writer_to_test.go index 0183ad1..709109b 100644 --- a/writer_to_test.go +++ b/writer_to_test.go @@ -8,7 +8,6 @@ import ( "github.com/jimeh/go-render" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) type mockWriterTo struct { @@ -16,6 +15,8 @@ type mockWriterTo struct { err error } +var _ io.WriterTo = (*mockWriterTo)(nil) + func (m *mockWriterTo) WriteTo(w io.Writer) (int64, error) { n, err := w.Write([]byte(m.value)) @@ -59,16 +60,12 @@ func TestWriterTo_Render(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { wt := &render.WriterTo{} - - var err error - var got string w := &bytes.Buffer{} - err = wt.Render(w, tt.value) - got = w.String() + err := wt.Render(w, tt.value) + got := w.String() if tt.wantErr != "" { - require.Error(t, err) assert.EqualError(t, err, tt.wantErr) } for _, e := range tt.wantErrIs { @@ -76,7 +73,7 @@ func TestWriterTo_Render(t *testing.T) { } if tt.wantErr == "" && len(tt.wantErrIs) == 0 { - require.NoError(t, err) + assert.NoError(t, err) assert.Equal(t, tt.want, got) } }) diff --git a/xml_test.go b/xml_test.go index 1209270..7f28913 100644 --- a/xml_test.go +++ b/xml_test.go @@ -3,13 +3,33 @@ package render_test import ( "bytes" "encoding/xml" + "errors" "testing" "github.com/jimeh/go-render" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) +type mockXMLMarshaler struct { + elm string + err error +} + +var _ xml.Marshaler = (*mockXMLMarshaler)(nil) + +func (mxm *mockXMLMarshaler) MarshalXML( + e *xml.Encoder, + start xml.StartElement, +) error { + err := e.EncodeElement(mxm.elm, start) + + if mxm.err != nil { + return mxm.err + } + + return err +} + func TestXML_Render(t *testing.T) { tests := []struct { name string @@ -33,15 +53,14 @@ func TestXML_Render(t *testing.T) { { name: "simple object with pretty", pretty: true, - indent: " ", value: struct { XMLName xml.Name `xml:"user"` Age int `xml:"age"` }{Age: 30}, - want: "\n 30\n", + want: "\n 30\n", }, { - name: "with prefix and indent", + name: "pretty with prefix and indent", pretty: true, prefix: "//", indent: "\t", @@ -51,6 +70,28 @@ func TestXML_Render(t *testing.T) { }{Age: 30}, want: "//\n//\t30\n//", }, + { + name: "prefix and indent without pretty", + pretty: false, + prefix: "//", + indent: "\t", + value: struct { + XMLName xml.Name `xml:"user"` + Age int `xml:"age"` + }{Age: 30}, + want: `30`, + }, + { + name: "implements xml.Marshaler", + value: &mockXMLMarshaler{elm: "test string"}, + want: "test string", + }, + { + name: "error from xml.Marshaler", + value: &mockXMLMarshaler{err: errors.New("mock error")}, + wantErr: "render: mock error", + wantErrIs: []error{render.Err}, + }, { name: "invalid value", pretty: false, @@ -67,12 +108,12 @@ func TestXML_Render(t *testing.T) { Prefix: tt.prefix, Indent: tt.indent, } - var buf bytes.Buffer + err := x.Render(&buf, tt.value) + got := buf.String() if tt.wantErr != "" { - require.Error(t, err) assert.EqualError(t, err, tt.wantErr) } for _, e := range tt.wantErrIs { @@ -80,8 +121,7 @@ func TestXML_Render(t *testing.T) { } if tt.wantErr == "" && len(tt.wantErrIs) == 0 { - require.NoError(t, err) - got := buf.String() + assert.NoError(t, err) assert.Equal(t, tt.want, got) } }) diff --git a/yaml_test.go b/yaml_test.go index 4065875..2b930dd 100644 --- a/yaml_test.go +++ b/yaml_test.go @@ -2,19 +2,33 @@ package render_test import ( "bytes" + "errors" "testing" "github.com/jimeh/go-render" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) +type mockYAMLMarshaler struct { + val any + err error +} + +var _ yaml.Marshaler = (*mockYAMLMarshaler)(nil) + +func (m *mockYAMLMarshaler) MarshalYAML() (any, error) { + return m.val, m.err +} + func TestYAML_Render(t *testing.T) { tests := []struct { name string indent int value interface{} want string + wantErr string + wantErrIs []error wantPanic string }{ { @@ -44,6 +58,17 @@ func TestYAML_Render(t *testing.T) { }, want: "user:\n age: 30\n name: John Doe\n", }, + { + name: "implements yaml.Marshaler", + value: &mockYAMLMarshaler{val: map[string]int{"age": 30}}, + want: "age: 30\n", + }, + { + name: "error from yaml.Marshaler", + value: &mockYAMLMarshaler{err: errors.New("mock error")}, + wantErr: "render: mock error", + wantErrIs: []error{render.Err}, + }, { name: "invalid value", indent: 0, @@ -51,7 +76,6 @@ func TestYAML_Render(t *testing.T) { wantPanic: "cannot marshal type: chan int", }, } - for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { j := &render.YAML{ @@ -70,11 +94,21 @@ func TestYAML_Render(t *testing.T) { err = j.Render(&buf, tt.value) }() + got := buf.String() + if tt.wantPanic != "" { assert.Equal(t, tt.wantPanic, panicRes) - } else { - require.NoError(t, err) - got := buf.String() + } + if tt.wantErr != "" { + assert.EqualError(t, err, tt.wantErr) + } + for _, e := range tt.wantErrIs { + assert.ErrorIs(t, err, e) + } + + if tt.wantPanic == "" && + tt.wantErr == "" && len(tt.wantErrIs) == 0 { + assert.NoError(t, err) assert.Equal(t, tt.want, got) } })