Skip to content

Commit cc025d0

Browse files
authored
Merge pull request #5 from roanutil/non-main-queue
Fix parent context save bug. Change tests to use private context.
2 parents 12fe604 + 6cea743 commit cc025d0

7 files changed

Lines changed: 147 additions & 135 deletions

File tree

Sources/CoreDataRepository/CoreDataRepository+CRUD.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ extension CoreDataRepository {
3333
object.create(from: item)
3434
let result: Result<NSManagedObject, CoreDataRepositoryError> = .success(object)
3535
return result
36-
.map(to: Model.RepoManaged.self, context: scratchPad)
36+
.map(to: Model.RepoManaged.self)
3737
.save(context: scratchPad)
3838
.map(\.asUnmanaged)
3939
}
@@ -55,7 +55,7 @@ extension CoreDataRepository {
5555
promise(
5656
Self.getObjectId(fromUrl: url, context: readContext)
5757
.mapToNSManagedObject(context: readContext)
58-
.map(to: Model.RepoManaged.self, context: readContext)
58+
.map(to: Model.RepoManaged.self)
5959
.map(\.asUnmanaged)
6060
)
6161
}
@@ -83,7 +83,7 @@ extension CoreDataRepository {
8383
scratchPad.transactionAuthor = transactionAuthor
8484
return Self.getObjectId(fromUrl: url, context: scratchPad)
8585
.mapToNSManagedObject(context: scratchPad)
86-
.map(to: Model.RepoManaged.self, context: scratchPad)
86+
.map(to: Model.RepoManaged.self)
8787
.map { repoManaged -> Model.RepoManaged in
8888
repoManaged.update(from: item)
8989
return repoManaged
@@ -195,7 +195,7 @@ extension CoreDataRepository {
195195
readContext.performAndWait {
196196
let result = Self.getObjectId(fromUrl: url, context: readContext)
197197
.mapToNSManagedObject(context: readContext)
198-
.map(to: T.self, context: readContext)
198+
.map(to: T.self)
199199
promise(result)
200200
}
201201
}.eraseToAnyPublisher()

Sources/CoreDataRepository/Result+CRUDHelpers.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ extension Result where Success == NSManagedObjectID, Failure == CoreDataReposito
2626
}
2727

2828
extension Result where Success == NSManagedObject, Failure == CoreDataRepositoryError {
29-
func map<T>(to _: T.Type, context _: NSManagedObjectContext) -> Result<T, CoreDataRepositoryError>
29+
func map<T>(to _: T.Type) -> Result<T, CoreDataRepositoryError>
3030
where T: RepositoryManagedModel
3131
{
3232
flatMap { _object in
@@ -44,9 +44,15 @@ extension Result where Failure == CoreDataRepositoryError {
4444
do {
4545
try context.save()
4646
if let parentContext = context.parent {
47-
try DispatchQueue.main.sync {
48-
try parentContext.save()
47+
var result: Result<Success, CoreDataRepositoryError> = .success(success)
48+
parentContext.performAndWait {
49+
do {
50+
try parentContext.save()
51+
} catch {
52+
result = .failure(.coreData(error as NSError))
53+
}
4954
}
55+
return result
5056
}
5157
return .success(success)
5258
} catch {

Tests/CoreDataRepositoryTests/AggregateRepositoryTests.swift

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,25 +34,21 @@ final class AggregateRepositoryTests: CoreDataXCTestCase {
3434
Movie(id: UUID(), title: "E", releaseDate: Date(), boxOffice: 50),
3535
]
3636
var objectIDs = [NSManagedObjectID]()
37-
var _repository: CoreDataRepository?
38-
var repository: CoreDataRepository { _repository! }
3937

4038
override func setUpWithError() throws {
4139
try super.setUpWithError()
42-
_repository = CoreDataRepository(context: viewContext)
43-
objectIDs = movies.map { $0.asRepoManaged(in: self.viewContext).objectID }
44-
try! viewContext.save()
40+
objectIDs = try movies.map { $0.asRepoManaged(in: try self.viewContext()).objectID }
41+
try viewContext().save()
4542
}
4643

4744
override func tearDownWithError() throws {
4845
try super.tearDownWithError()
49-
_repository = nil
5046
objectIDs = []
5147
}
5248

5349
func testCountSuccess() throws {
5450
let exp = expectation(description: "Get count of movies from CoreData")
55-
let result: AnyPublisher<[[String: Int]], CoreDataRepositoryError> = repository
51+
let result: AnyPublisher<[[String: Int]], CoreDataRepositoryError> = try repository()
5652
.count(predicate: NSPredicate(value: true), entityDesc: RepoMovie.entity())
5753
var values: [[String: Int]] = []
5854
result.subscribe(on: backgroundQueue)
@@ -77,7 +73,7 @@ final class AggregateRepositoryTests: CoreDataXCTestCase {
7773
func testSumSuccess() throws {
7874
let exp = expectation(description: "Get sum of CoreData Movies boxOffice")
7975
var values: [[String: Decimal]] = []
80-
let result: AnyPublisher<[[String: Decimal]], CoreDataRepositoryError> = repository.sum(
76+
let result: AnyPublisher<[[String: Decimal]], CoreDataRepositoryError> = try repository().sum(
8177
predicate: NSPredicate(value: true),
8278
entityDesc: RepoMovie.entity(),
8379
attributeDesc: RepoMovie.entity().attributesByName.values.first(where: { $0.name == "boxOffice" })!
@@ -107,7 +103,7 @@ final class AggregateRepositoryTests: CoreDataXCTestCase {
107103
func testAverageSuccess() throws {
108104
let exp = expectation(description: "Get average of CoreData Movies boxOffice")
109105
var values: [[String: Decimal]] = []
110-
let result: AnyPublisher<[[String: Decimal]], CoreDataRepositoryError> = repository.average(
106+
let result: AnyPublisher<[[String: Decimal]], CoreDataRepositoryError> = try repository().average(
111107
predicate: NSPredicate(value: true),
112108
entityDesc: RepoMovie.entity(),
113109
attributeDesc: RepoMovie.entity().attributesByName.values.first(where: { $0.name == "boxOffice" })!
@@ -137,7 +133,7 @@ final class AggregateRepositoryTests: CoreDataXCTestCase {
137133
func testMinSuccess() throws {
138134
let exp = expectation(description: "Get average of CoreData Movies boxOffice")
139135
var values: [[String: Decimal]] = []
140-
let result: AnyPublisher<[[String: Decimal]], CoreDataRepositoryError> = repository.min(
136+
let result: AnyPublisher<[[String: Decimal]], CoreDataRepositoryError> = try repository().min(
141137
predicate: NSPredicate(value: true),
142138
entityDesc: RepoMovie.entity(),
143139
attributeDesc: RepoMovie.entity().attributesByName.values.first(where: { $0.name == "boxOffice" })!
@@ -167,7 +163,7 @@ final class AggregateRepositoryTests: CoreDataXCTestCase {
167163
func testMaxSuccess() throws {
168164
let exp = expectation(description: "Get average of CoreData Movies boxOffice")
169165
var values: [[String: Decimal]] = []
170-
let result: AnyPublisher<[[String: Decimal]], CoreDataRepositoryError> = repository.max(
166+
let result: AnyPublisher<[[String: Decimal]], CoreDataRepositoryError> = try repository().max(
171167
predicate: NSPredicate(value: true),
172168
entityDesc: RepoMovie.entity(),
173169
attributeDesc: RepoMovie.entity().attributesByName.values.first(where: { $0.name == "boxOffice" })!

Tests/CoreDataRepositoryTests/BatchRepositoryTests.swift

Lines changed: 29 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,9 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
4949
]
5050
}()
5151

52-
var _repository: CoreDataRepository?
53-
var repository: CoreDataRepository { _repository! }
54-
55-
override func setUp() {
56-
super.setUp()
57-
_repository = CoreDataRepository(context: viewContext)
58-
}
59-
60-
override func tearDown() {
61-
super.tearDown()
62-
_repository = nil
63-
}
64-
6552
func mapDictToRepoMovie(_ dict: [String: Any]) throws -> RepoMovie {
6653
try mapDictToMovie(dict)
67-
.asRepoManaged(in: viewContext)
54+
.asRepoManaged(in: try viewContext())
6855
}
6956

7057
func mapDictToMovie(_ dict: [String: Any]) throws -> Movie {
@@ -76,12 +63,12 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
7663

7764
func testInsertSuccess() throws {
7865
let fetchRequest = NSFetchRequest<RepoMovie>(entityName: "RepoMovie")
79-
let count = try viewContext.count(for: fetchRequest)
66+
let count = try viewContext().count(for: fetchRequest)
8067
XCTAssert(count == 0, "Count of objects in CoreData should be zero at the start of each test.")
8168

8269
let exp = expectation(description: "Successfully batch insert movies.")
8370
let request = NSBatchInsertRequest(entityName: try XCTUnwrap(RepoMovie.entity().name), objects: movies)
84-
repository.insert(request)
71+
try repository().insert(request)
8572
.subscribe(on: backgroundQueue)
8673
.receive(on: mainQueue)
8774
.sink(
@@ -101,7 +88,7 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
10188
.store(in: &cancellables)
10289
wait(for: [exp], timeout: 5)
10390

104-
let data = try viewContext.fetch(fetchRequest)
91+
let data = try viewContext().fetch(fetchRequest)
10592
XCTAssert(
10693
data.map { $0.title ?? "" }.sorted() == ["A", "B", "C", "D", "E"],
10794
"Inserted titles should match expectation"
@@ -110,15 +97,15 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
11097

11198
func testInsertFailure() throws {
11299
let fetchRequest = NSFetchRequest<RepoMovie>(entityName: "RepoMovie")
113-
let count = try viewContext.count(for: fetchRequest)
100+
let count = try viewContext().count(for: fetchRequest)
114101
XCTAssert(count == 0, "Count of objects in CoreData should be zero at the start of each test.")
115102

116103
let exp = expectation(description: "Fail to batch insert movies.")
117104
let request = NSBatchInsertRequest(
118105
entityName: try XCTUnwrap(RepoMovie.entity().name),
119106
objects: failureInsertMovies
120107
)
121-
repository.insert(request)
108+
try repository().insert(request)
122109
.subscribe(on: backgroundQueue)
123110
.receive(on: mainQueue)
124111
.sink(
@@ -136,18 +123,18 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
136123
.store(in: &cancellables)
137124
wait(for: [exp], timeout: 5)
138125

139-
let data = try viewContext.fetch(fetchRequest)
126+
let data = try viewContext().fetch(fetchRequest)
140127
assert(data.map { $0.title ?? "" }.sorted() == [], "There should be no inserted values.")
141128
}
142129

143130
func testCreateSuccess() throws {
144131
let fetchRequest = NSFetchRequest<RepoMovie>(entityName: "RepoMovie")
145-
let count = try viewContext.count(for: fetchRequest)
132+
let count = try viewContext().count(for: fetchRequest)
146133
XCTAssert(count == 0, "Count of objects in CoreData should be zero at the start of each test.")
147134

148135
let exp = expectation(description: "Successfully batch insert movies.")
149136
let newMovies = try movies.map(mapDictToMovie(_:))
150-
let publisher: AnyPublisher<(success: [Movie], failed: [Movie]), Never> = repository.create(newMovies)
137+
let publisher: AnyPublisher<(success: [Movie], failed: [Movie]), Never> = try repository().create(newMovies)
151138
publisher
152139
.subscribe(on: backgroundQueue)
153140
.receive(on: mainQueue)
@@ -168,7 +155,7 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
168155
.store(in: &cancellables)
169156
wait(for: [exp], timeout: 5)
170157

171-
let data = try viewContext.fetch(fetchRequest)
158+
let data = try viewContext().fetch(fetchRequest)
172159
XCTAssert(
173160
data.map { $0.title ?? "" }.sorted() == ["A", "B", "C", "D", "E"],
174161
"Inserted titles should match expectation"
@@ -177,17 +164,17 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
177164

178165
func testReadSuccess() throws {
179166
let fetchRequest = NSFetchRequest<RepoMovie>(entityName: "RepoMovie")
180-
let count = try viewContext.count(for: fetchRequest)
167+
let count = try viewContext().count(for: fetchRequest)
181168
XCTAssert(count == 0, "Count of objects in CoreData should be zero at the start of each test.")
182169

183170
let repoMovies = try movies
184171
.map(mapDictToRepoMovie(_:))
185-
try viewContext.save()
172+
try viewContext().save()
186173

187174
let exp = expectation(description: "Successfully batch update movies.")
188175
let urlsToRead = repoMovies.map(\.asUnmanaged).compactMap(\.url)
189176
var resultingMovies = [Movie]()
190-
let publisher: AnyPublisher<(success: [Movie], failed: [URL]), Never> = repository.read(urls: urlsToRead)
177+
let publisher: AnyPublisher<(success: [Movie], failed: [URL]), Never> = try repository().read(urls: urlsToRead)
191178
publisher
192179
.subscribe(on: backgroundQueue)
193180
.receive(on: mainQueue)
@@ -215,19 +202,19 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
215202

216203
func testUpdateSuccess() throws {
217204
let fetchRequest = NSFetchRequest<RepoMovie>(entityName: "RepoMovie")
218-
let count = try viewContext.count(for: fetchRequest)
205+
let count = try viewContext().count(for: fetchRequest)
219206
XCTAssert(count == 0, "Count of objects in CoreData should be zero at the start of each test.")
220207

221208
_ = try movies
222209
.map(mapDictToRepoMovie(_:))
223-
try viewContext.save()
210+
try viewContext().save()
224211

225212
let exp = expectation(description: "Successfully batch update movies.")
226213
let predicate = NSPredicate(value: true)
227214
let request = NSBatchUpdateRequest(entityName: try XCTUnwrap(RepoMovie.entity().name))
228215
request.predicate = predicate
229216
request.propertiesToUpdate = ["title": "Updated!", "boxOffice": 1]
230-
repository.update(request)
217+
try repository().update(request)
231218
.subscribe(on: backgroundQueue)
232219
.receive(on: mainQueue)
233220
.sink(
@@ -247,7 +234,7 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
247234
.store(in: &cancellables)
248235
wait(for: [exp], timeout: 5)
249236

250-
let data = try viewContext.fetch(fetchRequest)
237+
let data = try viewContext().fetch(fetchRequest)
251238
XCTAssert(
252239
data.map { $0.title ?? "" }.sorted() == ["Updated!", "Updated!", "Updated!", "Updated!", "Updated!"],
253240
"Updated titles should match request"
@@ -256,19 +243,19 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
256243

257244
func testAltUpdateSuccess() throws {
258245
let fetchRequest = NSFetchRequest<RepoMovie>(entityName: "RepoMovie")
259-
let count = try viewContext.count(for: fetchRequest)
246+
let count = try viewContext().count(for: fetchRequest)
260247
XCTAssert(count == 0, "Count of objects in CoreData should be zero at the start of each test.")
261248

262249
let repoMovies = try movies
263250
.map(mapDictToRepoMovie(_:))
264-
try viewContext.save()
251+
try viewContext().save()
265252

266253
let exp = expectation(description: "Successfully batch update movies.")
267254
var editedMovies = repoMovies.map(\.asUnmanaged)
268255
let newTitles = ["ZA", "ZB", "ZC", "ZD", "ZE"]
269256
var resultingMovies = [Movie]()
270257
newTitles.enumerated().forEach { index, title in editedMovies[index].title = title }
271-
repository.update(editedMovies)
258+
try repository().update(editedMovies)
272259
.subscribe(on: backgroundQueue)
273260
.receive(on: mainQueue)
274261
.sink(
@@ -295,20 +282,20 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
295282

296283
func testDeleteSuccess() throws {
297284
let fetchRequest = NSFetchRequest<RepoMovie>(entityName: "RepoMovie")
298-
let count = try viewContext.count(for: fetchRequest)
285+
let count = try viewContext().count(for: fetchRequest)
299286
XCTAssert(count == 0, "Count of objects in CoreData should be zero at the start of each test.")
300287

301288
_ = try movies
302289
.map(mapDictToRepoMovie(_:))
303-
try viewContext.save()
290+
try viewContext().save()
304291

305292
let exp = expectation(description: "Successfully batch delete movies.")
306293
let request =
307294
NSBatchDeleteRequest(fetchRequest: NSFetchRequest<NSFetchRequestResult>(entityName: try XCTUnwrap(
308295
RepoMovie
309296
.entity().name
310297
)))
311-
repository.delete(request)
298+
try repository().delete(request)
312299
.subscribe(on: backgroundQueue)
313300
.receive(on: mainQueue)
314301
.sink(
@@ -327,26 +314,26 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
327314
)
328315
.store(in: &cancellables)
329316
wait(for: [exp], timeout: 5)
330-
viewContext.reset()
317+
try viewContext().reset()
331318

332-
let data = try viewContext.fetch(fetchRequest)
319+
let data = try viewContext().fetch(fetchRequest)
333320
XCTAssert(data.map { $0.title ?? "" }.sorted() == [], "There should be no remaining values.")
334321
}
335322

336323
// TODO: Add test for delete failure
337324

338325
func testAltDeleteSuccess() throws {
339326
let fetchRequest = NSFetchRequest<RepoMovie>(entityName: "RepoMovie")
340-
let count = try viewContext.count(for: fetchRequest)
327+
let count = try viewContext().count(for: fetchRequest)
341328
XCTAssert(count == 0, "Count of objects in CoreData should be zero at the start of each test.")
342329

343330
let repoMovies = try movies
344331
.map(mapDictToRepoMovie(_:))
345-
try viewContext.save()
332+
try viewContext().save()
346333

347334
let exp = expectation(description: "Successfully batch update movies.")
348335
let urlsToDelete = repoMovies.map(\.asUnmanaged).compactMap(\.url)
349-
repository.delete(urls: urlsToDelete)
336+
try repository().delete(urls: urlsToDelete)
350337
.subscribe(on: backgroundQueue)
351338
.receive(on: mainQueue)
352339
.sink(
@@ -367,7 +354,7 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
367354
.store(in: &cancellables)
368355
wait(for: [exp], timeout: 5)
369356

370-
let data = try viewContext.fetch(fetchRequest)
357+
let data = try viewContext().fetch(fetchRequest)
371358
XCTAssert(data.map { $0.title ?? "" }.sorted() == [], "There should be no remaining values.")
372359
}
373360
}

0 commit comments

Comments
 (0)