-
Notifications
You must be signed in to change notification settings - Fork 0
/
store_test.go
169 lines (165 loc) · 4.26 KB
/
store_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/*
Author: Paul Côté
Last Change Author: Paul Côté
Last Date Changed: 2022/06/11
*/
package gux
import (
"reflect"
"sync"
"testing"
"time"
)
var (
testReducer Reducer = func(v interface{}, a Action) (interface{}, error) {
// assert v and a.Payload are integers
cState, ok := v.(int)
if !ok {
return nil, ErrInvalidStateType
}
payload, ok := a.Payload.(int)
if !ok {
return nil, ErrInvalidPayloadType
}
switch a.Type {
case "increment":
return cState + payload, nil
case "decrement":
return cState - payload, nil
default:
return nil, ErrInvalidAction
}
}
testInitialState int = 0
testIncrementAction = Action{
Type: "increment",
Payload: 1,
}
testDecrementAction = Action{
Type: "decrement",
Payload: 1,
}
listenerName string = "test"
listenerNameTwo string = "testtwo"
)
// TestStore will test the state store against many different scenarios
func TestStore(t *testing.T) {
store := CreateStore(testInitialState, testReducer)
// increment and assert state change
t.Run("valid increment", func(t *testing.T) {
err := store.Dispatch(testIncrementAction)
if err != nil {
t.Errorf("Unexpected error when dispatching: %v", err)
}
currentState := store.GetState()
cState, ok := currentState.(int)
if !ok {
t.Errorf("Invalid type, expected int, got: %v", reflect.TypeOf(cState))
}
if cState != 1 {
t.Errorf("Unexpected state, expected 1 got: %v", cState)
}
})
// valid decrement
t.Run("valid decrement", func(t *testing.T) {
err := store.Dispatch(testDecrementAction)
if err != nil {
t.Errorf("Unexpected error when dispatching: %v", err)
}
currentState := store.GetState()
cState, ok := currentState.(int)
if !ok {
t.Errorf("Invalid type, expected int, got: %v", reflect.TypeOf(cState))
}
if cState != 0 {
t.Errorf("Unexpected state, expected 1 got: %v", cState)
}
})
// invalid action
t.Run("invalid action", func(t *testing.T) {
err := store.Dispatch(Action{Type: "invalid", Payload: 2000})
if err != ErrInvalidAction {
t.Errorf("Unexpected error: %v", err)
}
})
// invalid payload type
t.Run("invalid payload", func(t *testing.T) {
err := store.Dispatch(Action{Type: "increment", Payload: int64(1)})
if err != ErrInvalidPayloadType {
t.Errorf("Unexpected error: %v", err)
}
})
// testing subscribe and unsubscribe
t.Run("subscribe unsubscribe", func(t *testing.T) {
var wg sync.WaitGroup
stateChangeChan, unsub, _ := store.Subscribe(listenerName)
ticker := time.NewTicker(time.Duration(int64(2)) * time.Second)
defer ticker.Stop()
// We should get a state change update before the first tick
wg.Add(1)
go func() {
defer wg.Done()
select {
case <-ticker.C:
t.Errorf("Timeout error: received ticker signal before stateChange")
case <-stateChangeChan:
}
return
}()
err := store.Dispatch(testIncrementAction)
if err != nil {
t.Errorf("Unexpected error when dispatching: %v", err)
}
wg.Wait()
// now we shouldn't get any state change updates and instead get a tick signal
unsub()
wg.Add(1)
go func() {
defer wg.Done()
select {
case <-ticker.C:
case <-stateChangeChan:
t.Errorf("Error: received stateChange signal")
}
return
}()
wg.Wait()
})
t.Run("two subscribers same name", func(t *testing.T) {
_, unsub, _ := store.Subscribe(listenerNameTwo)
defer unsub()
_, _, err := store.Subscribe(listenerNameTwo)
if err != ErrAlreadySubscribed {
t.Errorf("Unexpected error when subscribing twice with the same name: %v", err)
}
})
// test for concurrent write/read
t.Run("concurrency", func(t *testing.T) {
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
err := store.Dispatch(testIncrementAction)
if err != nil {
t.Errorf("Unexpected error when dispatching: %v", err)
}
currentState := store.GetState()
cState, ok := currentState.(int)
if !ok {
t.Errorf("Invalid type, expected int, got: %v", reflect.TypeOf(cState))
}
t.Logf("State: %v", cState)
}()
}
wg.Wait()
currentState := store.GetState()
cState, ok := currentState.(int)
if !ok {
t.Errorf("Invalid type, expected int, got: %v", reflect.TypeOf(cState))
}
if cState != 101 {
t.Errorf("Unexpected state value: %v", cState)
}
})
}