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 }