github.com/shoshinnikita/budget-manager@v0.7.1-0.20220131195411-8c46ff1c6778/internal/pkg/money/money_test.go (about) 1 package money_test 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "math" 7 "testing" 8 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/require" 11 12 . "github.com/ShoshinNikita/budget-manager/internal/pkg/money" 13 ) 14 15 func TestConvertMoney(t *testing.T) { 16 t.Run("int to int", testConvertMoney_IntToInt) 17 t.Run("float to float", testConvertMoney_FloatToFloat) 18 t.Run("int to float", testConvertMoney_IntToFloat) 19 t.Run("float to int", testConvertMoney_FloatToInt) 20 } 21 22 func testConvertMoney_IntToInt(t *testing.T) { 23 t.Parallel() 24 25 require := require.New(t) 26 27 tests := []struct { 28 in int64 29 res Money 30 converted int64 31 }{ 32 {in: -20, res: Money(-2000), converted: -20}, 33 {in: 0, res: Money(0), converted: 0}, 34 {in: 15, res: Money(1500), converted: 15}, 35 {in: 1000000, res: Money(100000000), converted: 1000000}, 36 } 37 38 for _, tt := range tests { 39 res := FromInt(tt.in) 40 require.Equal(tt.res, res) 41 require.Equal(tt.converted, res.Int()) 42 } 43 } 44 45 func testConvertMoney_FloatToFloat(t *testing.T) { 46 t.Parallel() 47 48 require := require.New(t) 49 50 tests := []struct { 51 in float64 52 res Money 53 converted float64 54 }{ 55 {in: -20.50, res: Money(-2050), converted: -20.50}, 56 {in: 0, res: Money(0), converted: 0}, 57 {in: 0.75, res: Money(75), converted: 0.75}, 58 {in: 15.30, res: Money(1530), converted: 15.30}, 59 {in: 1000000.87, res: Money(100000087), converted: 1000000.87}, 60 {in: 69.99, res: Money(6999), converted: 69.99}, 61 {in: 17.83, res: Money(1783), converted: 17.83}, 62 } 63 64 for _, tt := range tests { 65 res := FromFloat(tt.in) 66 require.Equal(tt.res, res) 67 require.Equal(tt.converted, res.Float()) 68 } 69 } 70 71 func testConvertMoney_IntToFloat(t *testing.T) { 72 t.Parallel() 73 74 require := require.New(t) 75 76 tests := []struct { 77 in int64 78 res Money 79 converted float64 80 }{ 81 {in: -20, res: Money(-2000), converted: -20}, 82 {in: 0, res: Money(0), converted: 0}, 83 {in: 15, res: Money(1500), converted: 15}, 84 {in: 1000000, res: Money(100000000), converted: 1000000}, 85 } 86 87 for _, tt := range tests { 88 res := FromInt(tt.in) 89 require.Equal(tt.res, res) 90 require.Equal(tt.converted, res.Float()) 91 } 92 } 93 94 func testConvertMoney_FloatToInt(t *testing.T) { 95 t.Parallel() 96 97 require := require.New(t) 98 99 tests := []struct { 100 in float64 101 res Money 102 converted int64 103 }{ 104 {in: -20.5, res: Money(-2050), converted: -20}, 105 {in: 0.30, res: Money(30), converted: 0}, 106 {in: 15, res: Money(1500), converted: 15}, 107 {in: 1000000.87, res: Money(100000087), converted: 1000000}, 108 } 109 110 for _, tt := range tests { 111 res := FromFloat(tt.in) 112 require.Equal(tt.res, res) 113 require.Equal(tt.converted, res.Int()) 114 } 115 } 116 117 func TestAdd(t *testing.T) { 118 t.Parallel() 119 120 require := require.New(t) 121 122 tests := []struct { 123 origin int64 124 125 add Money 126 res Money 127 }{ 128 {origin: -150, add: Money(2000), res: Money(-13000)}, 129 {origin: 0, add: Money(2000), res: Money(2000)}, 130 {origin: 150, add: Money(2000), res: Money(17000)}, 131 // Add negative 132 {origin: 150, add: Money(-2000), res: Money(13000)}, 133 } 134 135 for _, tt := range tests { 136 origin := FromInt(tt.origin) 137 138 res := origin.Add(tt.add) 139 require.Equal(tt.res, res) 140 } 141 } 142 143 func TestSub(t *testing.T) { 144 t.Parallel() 145 146 require := require.New(t) 147 148 tests := []struct { 149 origin int64 150 151 sub Money 152 res Money 153 }{ 154 {origin: -150, sub: Money(2000), res: Money(-17000)}, 155 {origin: 0, sub: Money(2000), res: Money(-2000)}, 156 {origin: 150, sub: Money(2000), res: Money(13000)}, 157 // Sub negative 158 {origin: 150, sub: Money(-2000), res: Money(17000)}, 159 } 160 161 for _, tt := range tests { 162 origin := FromInt(tt.origin) 163 164 res := origin.Sub(tt.sub) 165 require.Equal(tt.res, res) 166 } 167 } 168 169 func TestDivide(t *testing.T) { 170 t.Parallel() 171 172 t.Run("common divides", func(t *testing.T) { 173 require := require.New(t) 174 175 tests := []struct { 176 origin Money 177 n int64 178 res Money 179 resInt int64 180 resFloat float64 181 }{ 182 {origin: FromInt(1500), n: 1, res: Money(150000), resInt: 1500, resFloat: 1500}, 183 {origin: FromInt(1500), n: 5, res: Money(30000), resInt: 300, resFloat: 300}, 184 {origin: FromInt(1500), n: 7, res: Money(21428), resInt: 214, resFloat: 214.28}, 185 } 186 187 for _, tt := range tests { 188 res := tt.origin.Div(tt.n) 189 require.Equal(tt.res, res) 190 require.Equal(tt.resInt, res.Int()) 191 require.Equal(tt.resFloat, res.Float()) 192 } 193 }) 194 195 // Special case 196 t.Run("divide by zero", func(t *testing.T) { 197 require := require.New(t) 198 199 defer func() { 200 r := recover() 201 require.NotNil(r) 202 }() 203 204 FromFloat(120.5).Div(0) 205 }) 206 } 207 208 func TestRound(t *testing.T) { 209 t.Parallel() 210 211 tests := []float64{ 212 1, 1.4, 1.5, 1.6, 213 0, 214 -1, -1.4, -1.5, -1.6, 215 } 216 for _, tt := range tests { 217 wantRound := int64(math.Round(tt)) 218 gotRound := FromFloat(tt).Round().Int() 219 220 assert.Equalf(t, wantRound, gotRound, "test: round %f", tt) 221 222 wantCeil := int64(math.Ceil(tt)) 223 gotCeil := FromFloat(tt).Ceil().Int() 224 225 assert.Equalf(t, wantCeil, gotCeil, "test: ceil %f", tt) 226 227 wantFloor := int64(math.Floor(tt)) 228 gotFloor := FromFloat(tt).Floor().Int() 229 230 assert.Equalf(t, wantFloor, gotFloor, "test: floor %f", tt) 231 } 232 } 233 234 func TestJSON(t *testing.T) { 235 t.Run("Marshal", testJSON_Marshal) 236 t.Run("Unmarshal", testJSON_Unmarshal) 237 } 238 239 func testJSON_Marshal(t *testing.T) { 240 t.Parallel() 241 242 type testStruct struct { 243 Money Money `json:"money"` 244 } 245 246 require := require.New(t) 247 248 tests := []struct { 249 input testStruct 250 want string 251 }{ 252 { 253 input: testStruct{Money: FromInt(357)}, 254 want: `{"money":357.00}`, 255 }, 256 { 257 input: testStruct{Money: FromFloat(154.30)}, 258 want: `{"money":154.30}`, 259 }, 260 { 261 input: testStruct{Money: FromFloat(0.07)}, 262 want: `{"money":0.07}`, 263 }, 264 { 265 input: testStruct{Money: FromFloat(15.073)}, 266 want: `{"money":15.07}`, 267 }, 268 { 269 input: testStruct{Money: FromFloat(15.078)}, 270 want: `{"money":15.07}`, 271 }, 272 } 273 274 for _, tt := range tests { 275 data, err := json.Marshal(tt.input) 276 require.Nil(err) 277 require.Equal(tt.want, string(data)) 278 } 279 } 280 281 func testJSON_Unmarshal(t *testing.T) { 282 t.Parallel() 283 284 type testStruct struct { 285 Money Money `json:"money"` 286 } 287 288 require := require.New(t) 289 290 tests := []struct { 291 input string 292 want testStruct 293 }{ 294 { 295 input: `{"money":357}`, 296 want: testStruct{Money: FromInt(357)}, 297 }, 298 { 299 input: `{"money":154.30}`, 300 want: testStruct{Money: FromFloat(154.30)}, 301 }, 302 { 303 input: `{"money":0.07}`, 304 want: testStruct{Money: FromFloat(0.07)}, 305 }, 306 { 307 input: `{"money":"test"}`, 308 want: testStruct{Money: FromInt(0)}, 309 }, 310 } 311 312 for _, tt := range tests { 313 var res testStruct 314 315 // Ignore errors 316 _ = json.Unmarshal([]byte(tt.input), &res) 317 require.Equal(tt.want, res) 318 } 319 } 320 321 func TestFormat(t *testing.T) { 322 t.Parallel() 323 324 require := require.New(t) 325 326 tests := []struct { 327 input Money 328 format string // default format is '%v' 329 want string 330 }{ 331 {input: FromInt(357), want: "357.00"}, 332 {input: FromInt(-357), want: "-357.00"}, 333 {input: FromFloat(154.30), want: "154.30"}, 334 {input: FromFloat(-154.30), want: "-154.30"}, 335 {input: FromFloat(0.07), want: "0.07"}, 336 {input: FromFloat(15.073), want: "15.07"}, 337 {input: FromFloat(15.078), want: "15.07"}, 338 // Check grouping 339 {input: FromInt(1_500), want: "1 500.00"}, 340 {input: FromInt(-1_500), want: "-1 500.00"}, 341 {input: FromInt(15_000), want: "15 000.00"}, 342 {input: FromInt(-15_000), want: "-15 000.00"}, 343 {input: FromInt(150_000), want: "150 000.00"}, 344 {input: FromInt(-150_000), want: "-150 000.00"}, 345 {input: FromFloat(1_500_000.05), want: "1 500 000.05"}, 346 {input: FromFloat(-1_500_000.05), want: "-1 500 000.05"}, 347 {input: FromFloat(15_000_000.05), want: "15 000 000.05"}, 348 {input: FromFloat(-15_000_000.05), want: "-15 000 000.05"}, 349 {input: FromFloat(150_000_000.05), want: "150 000 000.05"}, 350 {input: FromFloat(-150_000_000.05), want: "-150 000 000.05"}, 351 {input: FromFloat(1_500_000_000.05), want: "1 500 000 000.05"}, 352 {input: FromFloat(-1_500_000_000.05), want: "-1 500 000 000.05"}, 353 {input: FromFloat(15_000_000_000.05), want: "15 000 000 000.05"}, 354 {input: FromFloat(-15_000_000_000.05), want: "-15 000 000 000.05"}, 355 {input: FromFloat(150_000_000_000.00), want: "150 000 000 000.00"}, 356 {input: FromFloat(-150_000_000_000.00), want: "-150 000 000 000.00"}, 357 {input: FromInt(1_500_000_000_000), want: "1 500 000 000 000.00"}, 358 {input: FromInt(-1_500_000_000_000), want: "-1 500 000 000 000.00"}, 359 // Check formats 360 {input: FromFloat(0.05), format: "%d", want: "0"}, 361 {input: FromFloat(-0.05), format: "%d", want: "0"}, 362 {input: FromFloat(0.05), format: "%f", want: "0.05"}, 363 {input: FromFloat(-0.05), format: "%f", want: "-0.05"}, 364 {input: FromFloat(1_500.25), format: "%d", want: "1500"}, 365 {input: FromFloat(-1_500.25), format: "%d", want: "-1500"}, 366 {input: FromFloat(1_500.25), format: "%f", want: "1500.25"}, 367 {input: FromFloat(-1_500.25), format: "%f", want: "-1500.25"}, 368 } 369 370 for _, tt := range tests { 371 var s string 372 if tt.format == "" { 373 tt.format = "%v" 374 } 375 376 s = fmt.Sprintf(tt.format, tt.input) 377 require.Equal(tt.want, s) 378 } 379 }