Skip to content
This repository was archived by the owner on May 13, 2020. It is now read-only.

Commit feeb80e

Browse files
committed
improve UT regarding Store thread safety
1 parent ceb78af commit feeb80e

File tree

4 files changed

+115
-42
lines changed

4 files changed

+115
-42
lines changed

RxReduceTests/ActionTests.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,38 +18,38 @@ class ActionTests: XCTestCase {
1818

1919
func testSynchronousAction() throws {
2020

21-
let increaseAction = IncreaseAction(increment: 10)
21+
let increaseAction = AppAction.increase(increment: 10)
2222

2323
let action = try increaseAction.toAsync().toBlocking().single()
24-
if let action = action as? IncreaseAction {
25-
XCTAssertEqual(10, action.increment)
24+
if case let AppAction.increase(increment) = action {
25+
XCTAssertEqual(10, increment)
2626
} else {
2727
XCTFail()
2828
}
2929
}
3030

3131
func testAsynchronousAction () throws {
3232

33-
let increaseAction = Observable<Action>.just(IncreaseAction(increment: 10))
33+
let increaseAction = Observable<Action>.just(AppAction.increase(increment: 10))
3434

3535
let action = try increaseAction.toAsync().toBlocking().single()
36-
if let action = action as? IncreaseAction {
37-
XCTAssertEqual(10, action.increment)
36+
if case let AppAction.increase(increment) = action {
37+
XCTAssertEqual(10, increment)
3838
} else {
3939
XCTFail()
4040
}
4141
}
4242

4343
func testArrayOfActions () throws {
4444

45-
let actions: [Action] = [IncreaseAction(increment: 10), IncreaseAction(increment: 20), IncreaseAction(increment: 30)]
45+
let actions: [Action] = [AppAction.increase(increment: 10), AppAction.increase(increment: 20), AppAction.increase(increment: 30)]
4646

4747
var initialIncrement = 10
4848
let actionsToTest = try actions.toAsync().toBlocking().toArray()
4949

5050
actionsToTest.forEach {
51-
if let action = $0 as? IncreaseAction {
52-
XCTAssertEqual(initialIncrement, action.increment)
51+
if case let AppAction.increase(increment) = $0 {
52+
XCTAssertEqual(initialIncrement, increment)
5353
initialIncrement += 10
5454
} else {
5555
XCTFail()
@@ -59,14 +59,14 @@ class ActionTests: XCTestCase {
5959

6060
func testArrayOfActionsWithObservable () throws {
6161

62-
let actions: [Action] = [IncreaseAction(increment: 10), Observable<Action>.just(IncreaseAction(increment: 20)), IncreaseAction(increment: 30)]
62+
let actions: [Action] = [AppAction.increase(increment: 10), Observable<Action>.just(AppAction.increase(increment: 20)), AppAction.increase(increment: 30)]
6363

6464
var initialIncrement = 10
6565
let actionsToTest = try actions.toAsync().toBlocking().toArray()
6666

6767
actionsToTest.forEach {
68-
if let action = $0 as? IncreaseAction {
69-
XCTAssertEqual(initialIncrement, action.increment)
68+
if case let AppAction.increase(increment) = $0 {
69+
XCTAssertEqual(initialIncrement, increment)
7070
initialIncrement += 10
7171
} else {
7272
XCTFail()

RxReduceTests/State/Actions.swift

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,9 @@
99
import Foundation
1010
import RxReduce
1111

12-
struct IncreaseAction: Action {
13-
let increment: Int
14-
}
15-
16-
struct DecreaseAction: Action {
17-
let decrement: Int
18-
}
19-
20-
struct ClearAction: Action {
21-
}
22-
23-
struct LogUserAction: Action {
24-
let user: String
12+
enum AppAction: Action {
13+
case increase(increment: Int)
14+
case decrease(decrement: Int)
15+
case logUser(user: String)
16+
case clear
2517
}

RxReduceTests/State/Reducers.swift

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import RxReduce
1111

1212
func counterReduce (state: TestState, action: Action) -> CounterState {
1313

14+
guard let action = action as? AppAction else { return state.counterState }
15+
1416
var currentCounter = 0
1517

1618
// we extract the current counter value from the current state
@@ -23,11 +25,11 @@ func counterReduce (state: TestState, action: Action) -> CounterState {
2325

2426
// according to the action we mutate the counter state
2527
switch action {
26-
case let action as IncreaseAction:
27-
return .increasing(currentCounter+action.increment)
28-
case let action as DecreaseAction:
29-
return .decreasing(currentCounter-action.decrement)
30-
case is ClearAction:
28+
case .increase(let increment):
29+
return .increasing(currentCounter+increment)
30+
case .decrease(let decrement):
31+
return .decreasing(currentCounter-decrement)
32+
case .clear:
3133
return .empty
3234
default:
3335
return state.counterState
@@ -36,11 +38,13 @@ func counterReduce (state: TestState, action: Action) -> CounterState {
3638

3739
func userReduce (state: TestState, action: Action) -> UserState {
3840

41+
guard let action = action as? AppAction else { return state.userState }
42+
3943
// according to the action we mutate the users state
4044
switch action {
41-
case let action as LogUserAction:
42-
return .loggedIn(name: action.user)
43-
case is ClearAction:
45+
case .logUser(let user):
46+
return .loggedIn(name: user)
47+
case .clear:
4448
return .loggedOut
4549
default:
4650
return state.userState

RxReduceTests/StoreTests.swift

Lines changed: 86 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ final class StoreTests: XCTestCase {
4141

4242
override func tearDown() {
4343
do {
44-
_ = try store.dispatch(action: ClearAction()).toBlocking(timeout: 1).single()
44+
_ = try store.dispatch(action: AppAction.clear).toBlocking(timeout: 1).single()
4545
} catch {
4646
XCTFail(error.localizedDescription)
4747
}
4848
}
4949

5050
func testSynchronousAction() throws {
5151

52-
let increaseAction = IncreaseAction(increment: 10)
52+
let increaseAction = AppAction.increase(increment: 10)
5353

5454
let state = try store
5555
.dispatch(action: increaseAction)
@@ -66,7 +66,7 @@ final class StoreTests: XCTestCase {
6666

6767
func testSubstateSubscription() throws {
6868

69-
let increaseAction = IncreaseAction(increment: 10)
69+
let increaseAction = AppAction.increase(increment: 10)
7070

7171
let counterState = try store
7272
.dispatch(action: increaseAction) { $0.counterState }
@@ -82,7 +82,7 @@ final class StoreTests: XCTestCase {
8282

8383
func testAsynchronousAction() throws {
8484

85-
let increaseAction = Observable<Action>.just(IncreaseAction(increment: 10))
85+
let increaseAction = Observable<Action>.just(AppAction.increase(increment: 10))
8686

8787
let state = try store
8888
.dispatch(action: increaseAction)
@@ -99,7 +99,7 @@ final class StoreTests: XCTestCase {
9999

100100
func testArrayOfActionsWithObservable () throws {
101101

102-
let actions: [Action] = [IncreaseAction(increment: 10), Observable<Action>.just(DecreaseAction(decrement: 20)), LogUserAction(user: "Spock")]
102+
let actions: [Action] = [AppAction.increase(increment: 10), Observable<Action>.just(AppAction.decrease(decrement: 20)), AppAction.logUser(user: "Spock")]
103103

104104
let statesToTest = try store.dispatch(action: actions).toBlocking().toArray()
105105

@@ -121,7 +121,7 @@ final class StoreTests: XCTestCase {
121121
func testStateObservable () throws {
122122
let exp = expectation(description: "State Observable Expectation")
123123

124-
let increaseAction = IncreaseAction(increment: 10)
124+
let increaseAction = AppAction.increase(increment: 10)
125125

126126
Observable.combineLatest(self.store.dispatch(action: increaseAction), self.store.state) { (newState1, newState2) -> (TestState, TestState) in
127127
return (newState1, newState2)
@@ -156,18 +156,18 @@ final class StoreTests: XCTestCase {
156156
// Given
157157
let exp = expectation(description: "Queue expectation")
158158
exp.expectedFulfillmentCount = 3
159-
let actionObservable = Observable<Action>.just (IncreaseAction(increment: 10))
159+
let actionObservable = Observable<Action>.just (AppAction.increase(increment: 10))
160160

161161
// When
162162
actionObservable
163-
.map { action -> DecreaseAction in
163+
.map { action -> AppAction in
164164
if let queueName = OperationQueue.current?.name!,
165165
queueName == "SUBSCRIBE_ON_QUEUE" {
166166
exp.fulfill()
167167
} else {
168168
XCTFail("Subscribe on Wrong Queue")
169169
}
170-
return DecreaseAction(decrement: 10)
170+
return AppAction.decrease(decrement: 10)
171171
}
172172
.flatMap{ (action) -> Observable<CounterState> in
173173

@@ -194,4 +194,81 @@ final class StoreTests: XCTestCase {
194194
// Then
195195
waitForExpectations(timeout: 1)
196196
}
197+
198+
func testDispatchActionSerialSynchronization () throws {
199+
// Given
200+
let exp = expectation(description: "Synchronization expectation")
201+
exp.expectedFulfillmentCount = 2
202+
let concurrentQueue = DispatchQueue(label: "com.rxswiftcommunity.rxreduce.concurrentqueue", qos: .userInitiated, attributes: [.concurrent])
203+
let sem = DispatchSemaphore(value: 0)
204+
var firstReducerStartTime = DispatchTime.now()
205+
var firstReducerEndTime = DispatchTime.now()
206+
var secondReducerStartTime = DispatchTime.now()
207+
var secondReducerEndTime = DispatchTime.now()
208+
209+
// Implements Reducers that have specific instructions for Thread lock and Time measurement
210+
func userReducer (state: TestState, action: Action) -> UserState {
211+
if case let AppAction.logUser(user) = action {
212+
firstReducerStartTime = DispatchTime.now()
213+
_ = sem.wait(timeout: DispatchTime(uptimeNanoseconds: 1000000000))
214+
exp.fulfill()
215+
firstReducerEndTime = DispatchTime.now()
216+
return UserState.loggedIn(name: user)
217+
}
218+
return state.userState
219+
}
220+
221+
func counterReducer (state: TestState, action: Action) -> CounterState {
222+
if case let AppAction.increase(increment) = action {
223+
secondReducerStartTime = DispatchTime.now()
224+
_ = sem.wait(timeout: DispatchTime(uptimeNanoseconds: 1000000000))
225+
exp.fulfill()
226+
secondReducerEndTime = DispatchTime.now()
227+
return CounterState.increasing(increment)
228+
}
229+
return state.counterState
230+
}
231+
232+
// Instantiating Mutators and Store using those 2 Reducers
233+
let userMutator = Mutator<TestState, UserState>(lens: userLens,
234+
reducer: userReducer)
235+
236+
let counterMutator = Mutator<TestState, CounterState>(lens: counterLens,
237+
reducer: counterReducer)
238+
239+
let syncTestsStore = Store<TestState>(withState: TestState(counterState: .empty, userState: .loggedOut))
240+
syncTestsStore.register(mutator: userMutator)
241+
syncTestsStore.register(mutator: counterMutator)
242+
243+
// When
244+
concurrentQueue.async {
245+
do {
246+
_ = try syncTestsStore.dispatch(action: AppAction.increase(increment: 10)).toBlocking().single()
247+
} catch {
248+
XCTFail()
249+
}
250+
}
251+
252+
concurrentQueue.async {
253+
do {
254+
_ = try syncTestsStore.dispatch(action: AppAction.logUser(user: "Spock")).toBlocking().single()
255+
} catch {
256+
XCTFail()
257+
}
258+
}
259+
260+
// Then
261+
waitForExpectations(timeout: 5) { error in
262+
if let error = error {
263+
XCTFail(error.localizedDescription)
264+
return
265+
}
266+
267+
if firstReducerStartTime < secondReducerStartTime {
268+
XCTAssertGreaterThanOrEqual(secondReducerStartTime, firstReducerEndTime)
269+
} else {
270+
XCTAssertGreaterThanOrEqual(firstReducerStartTime, secondReducerEndTime)
271+
}
272+
}
273+
}
197274
}

0 commit comments

Comments
 (0)