github.com/dranikpg/go-dto@v1.0.0/godto_test.go (about)

     1  package dto
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"reflect"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  )
    11  
    12  // ==================================== Types for tests =======================
    13  
    14  type Product struct {
    15  	Name    string
    16  	Country string
    17  	Price   float32
    18  }
    19  
    20  type ProductRef struct {
    21  	Product
    22  	Link string
    23  }
    24  
    25  type ShoppingCart struct {
    26  	Products []Product
    27  }
    28  
    29  type TaggedShoppingCart struct {
    30  	Products map[string][]Product
    31  }
    32  
    33  type RawPassword = string
    34  
    35  type User struct {
    36  	Name     string
    37  	Password RawPassword
    38  }
    39  
    40  // ==================================== Data for tests ========================
    41  
    42  var commonProducts = []Product{
    43  	{
    44  		Name:    "Shirt",
    45  		Price:   9.4,
    46  		Country: "US",
    47  	},
    48  	{
    49  		Name:    "Shoes",
    50  		Price:   17.3,
    51  		Country: "UK",
    52  	},
    53  	{
    54  		Name:    "Hat",
    55  		Price:   19.7,
    56  		Country: "IT",
    57  	},
    58  	{
    59  		Name:    "Bowtie",
    60  		Price:   5.1,
    61  		Country: "US",
    62  	},
    63  }
    64  
    65  var cartByCountries = TaggedShoppingCart{
    66  	Products: map[string][]Product{
    67  		"Europe":  {commonProducts[1], commonProducts[2]},
    68  		"America": {commonProducts[0], commonProducts[3]},
    69  	},
    70  }
    71  
    72  // ==================================== Tests =================================
    73  
    74  // Shallow structs with equal types
    75  func TestSimple(t *testing.T) {
    76  	var outNameAndPrice struct {
    77  		Name  string
    78  		Price float32
    79  	}
    80  	for _, product := range commonProducts {
    81  		err := Map(&outNameAndPrice, product)
    82  		assert.Nil(t, err)
    83  		assert.Equal(t, outNameAndPrice.Name, product.Name)
    84  		assert.Equal(t, outNameAndPrice.Price, product.Price)
    85  	}
    86  }
    87  
    88  // Struct with simple conversion (float to int)
    89  func TestSimpleConv(t *testing.T) {
    90  	var outPriceInt struct {
    91  		Price int
    92  	}
    93  	for _, product := range commonProducts {
    94  		err := Map(&outPriceInt, product)
    95  		assert.Nil(t, err)
    96  		assert.Equal(t, outPriceInt.Price, int(product.Price))
    97  	}
    98  }
    99  
   100  // ProductRef embeds Product
   101  func TestEmbedded(t *testing.T) {
   102  	var outNameAndLink struct {
   103  		Name string
   104  		Link string
   105  	}
   106  	for i, product := range commonProducts {
   107  		productRef := ProductRef{
   108  			Product: product,
   109  			Link:    fmt.Sprintf("/p/%v", i),
   110  		}
   111  		Map(&outNameAndLink, productRef)
   112  		assert.Equal(t, outNameAndLink.Name, productRef.Name)
   113  		assert.Equal(t, outNameAndLink.Link, productRef.Link)
   114  	}
   115  }
   116  
   117  // Map a slice of Products
   118  func TestSlice(t *testing.T) {
   119  	var outCart struct {
   120  		Products []struct {
   121  			Name string
   122  		}
   123  	}
   124  	testCart := ShoppingCart{
   125  		Products: commonProducts,
   126  	}
   127  
   128  	err := Map(&outCart, testCart)
   129  	assert.Nil(t, err)
   130  
   131  	assert.Equal(t, len(outCart.Products), len(testCart.Products))
   132  	for i, product := range outCart.Products {
   133  		assert.Equal(t, product.Name, testCart.Products[i].Name)
   134  	}
   135  }
   136  
   137  // Map a map of Product slices tagged by strings
   138  func TestMap(t *testing.T) {
   139  	var outCart struct {
   140  		Products map[string][]struct {
   141  			Name string
   142  		}
   143  	}
   144  	testCart := cartByCountries
   145  
   146  	err := Map(&outCart, testCart)
   147  	assert.Nil(t, err)
   148  
   149  	assert.Equal(t, len(outCart.Products), len(testCart.Products))
   150  	for key, list := range outCart.Products {
   151  		testList, ok := testCart.Products[key]
   152  		assert.True(t, ok)
   153  		assert.Equal(t, len(list), len(testList))
   154  		for i, product := range list {
   155  			assert.Equal(t, product.Name, testList[i].Name)
   156  		}
   157  	}
   158  }
   159  
   160  // Map a map to a slice (its values)
   161  func TestMapToSlice(t *testing.T) {
   162  	var outCart struct {
   163  		Products [][]struct {
   164  			Name string
   165  		}
   166  	}
   167  	testCart := cartByCountries
   168  
   169  	err := Map(&outCart, testCart)
   170  	assert.Nil(t, err)
   171  
   172  	assert.Equal(t, len(outCart.Products), len(testCart.Products))
   173  }
   174  
   175  // Map a map of slices to a slice (flatten)
   176  func TestMapSlicesToSlice(t *testing.T) {
   177  	var outCart struct {
   178  		Products []struct {
   179  			Name string
   180  		}
   181  	}
   182  	testCart := cartByCountries
   183  
   184  	err := Map(&outCart, testCart)
   185  	assert.Nil(t, err)
   186  
   187  	tcLen := 0
   188  	for _, v := range testCart.Products {
   189  		tcLen += len(v)
   190  	}
   191  	assert.Equal(t, len(outCart.Products), tcLen)
   192  }
   193  
   194  // Dereference a pointer to map a struct
   195  func TestPointerDeref(t *testing.T) {
   196  	var outCart struct {
   197  		Product struct {
   198  			Name string
   199  		}
   200  	}
   201  	var testCart struct {
   202  		Product *Product
   203  	}
   204  	testCart.Product = &commonProducts[0]
   205  
   206  	err := Map(&outCart, testCart)
   207  	assert.Nil(t, err)
   208  
   209  	assert.Equal(t, outCart.Product.Name, testCart.Product.Name)
   210  }
   211  
   212  // Conversion functions without errors and with mapper injection
   213  func TestConversionFunc(t *testing.T) {
   214  	type PasswordLen = int
   215  	var outUser struct {
   216  		Password PasswordLen
   217  	}
   218  	testUser := User{
   219  		Name:     "Bob",
   220  		Password: "Secret",
   221  	}
   222  
   223  	m := Mapper{}
   224  	m.AddConvFunc(func(p RawPassword, im *Mapper) PasswordLen {
   225  		assert.Equal(t, im, &m)
   226  		return len(p)
   227  	})
   228  
   229  	err := m.Map(&outUser, testUser)
   230  	assert.Nil(t, err)
   231  
   232  	assert.Equal(t, outUser.Password, len(testUser.Password))
   233  }
   234  
   235  // Inspect functions without errors and with mapper injection that change data
   236  func TestInspectFunc(t *testing.T) {
   237  	type ProductDTO struct {
   238  		Name      string
   239  		Expensive bool
   240  	}
   241  	var outCart struct {
   242  		Products []ProductDTO
   243  	}
   244  	testCart := ShoppingCart{
   245  		Products: commonProducts,
   246  	}
   247  
   248  	m := Mapper{}
   249  	m.AddInspectFunc(func(dto *ProductDTO, prod Product, im *Mapper) {
   250  		assert.Equal(t, im, &m)
   251  		assert.Equal(t, dto.Name, prod.Name)
   252  		dto.Expensive = prod.Price > 10
   253  	})
   254  
   255  	err := m.Map(&outCart, testCart)
   256  	assert.Nil(t, err)
   257  
   258  	assert.Equal(t, len(outCart.Products), len(testCart.Products))
   259  	for i, product := range outCart.Products {
   260  		assert.Equal(t, product.Expensive, testCart.Products[i].Price > 10)
   261  	}
   262  }
   263  
   264  func TestErrorNoValidMapping(t *testing.T) {
   265  	var outProduct struct {
   266  		Name int
   267  	}
   268  	err := Map(&outProduct, commonProducts[0])
   269  	assert.ErrorIs(t, err, ErrNoValidMapping{
   270  		ToType:   reflect.TypeOf(int(0)),
   271  		FromType: reflect.TypeOf(string("")),
   272  	})
   273  }
   274  
   275  // Propagate error from inspection function
   276  func TestErrorPropagation(t *testing.T) {
   277  	var outCart struct {
   278  		Products []struct {
   279  			Name string
   280  		}
   281  	}
   282  	testCart := ShoppingCart{
   283  		Products: commonProducts,
   284  	}
   285  
   286  	var testError = errors.New("Test error")
   287  
   288  	m := Mapper{}
   289  	m.AddInspectFunc(func(name *string) error {
   290  		return testError
   291  	})
   292  
   293  	err := m.Map(&outCart, testCart)
   294  	assert.Equal(t, err, testError)
   295  }
   296  
   297  // ==================================== Benchmarks ============================
   298  
   299  type benchCart = struct {
   300  	Products []struct {
   301  		Name string
   302  	}
   303  }
   304  
   305  func benchMakeTestCart(size int) ShoppingCart {
   306  	out := ShoppingCart{
   307  		Products: make([]Product, size),
   308  	}
   309  	for i := 0; i < size; i++ {
   310  		out.Products[i] = commonProducts[i%len(commonProducts)]
   311  	}
   312  	return out
   313  }
   314  
   315  // Benchmark without godto
   316  func BenchmarkSimpleMap(b *testing.B) {
   317  	var outCart benchCart
   318  	testCart := benchMakeTestCart(1000)
   319  	b.ResetTimer()
   320  
   321  	outCart.Products = make([]struct{ Name string }, len(testCart.Products))
   322  	for i, prod := range testCart.Products {
   323  		outCart.Products[i].Name = prod.Name
   324  	}
   325  }
   326  
   327  // Benchmark with godto
   328  func BenchmarkDtoMap(b *testing.B) {
   329  	var outCart benchCart
   330  	testCart := benchMakeTestCart(1000)
   331  	b.ResetTimer()
   332  
   333  	Map(&outCart, testCart)
   334  }