diff --git a/EssentialApp/EssentialApp.xcworkspace/xcshareddata/xcschemes/CI_iOS.xcscheme b/EssentialApp/EssentialApp.xcworkspace/xcshareddata/xcschemes/CI_iOS.xcscheme
index b5f14ef4..37505d43 100644
--- a/EssentialApp/EssentialApp.xcworkspace/xcshareddata/xcschemes/CI_iOS.xcscheme
+++ b/EssentialApp/EssentialApp.xcworkspace/xcshareddata/xcschemes/CI_iOS.xcscheme
@@ -139,6 +139,12 @@
ReferencedContainer = "container:../EssentialFeed/EssentialFeed.xcodeproj">
+
+
+
+
+
+
+
+
AnyDispatchQueueScheduler {
+ CoreDataFeedStoreScheduler(store: store).eraseToAnyScheduler()
+ }
+
+ private struct CoreDataFeedStoreScheduler: Scheduler {
+ let store: CoreDataFeedStore
+
+ var now: SchedulerTimeType { .init(.now()) }
+
+ var minimumTolerance: SchedulerTimeType.Stride { .zero }
+
+ func schedule(after date: DispatchQueue.SchedulerTimeType, interval: DispatchQueue.SchedulerTimeType.Stride, tolerance: DispatchQueue.SchedulerTimeType.Stride, options: DispatchQueue.SchedulerOptions?, _ action: @escaping () -> Void) -> any Cancellable {
+ if store.contextQueue == .main, Thread.isMainThread {
+ action()
+ } else {
+ store.perform(action)
+ }
+ return AnyCancellable {}
+ }
+
+ func schedule(after date: DispatchQueue.SchedulerTimeType, tolerance: DispatchQueue.SchedulerTimeType.Stride, options: DispatchQueue.SchedulerOptions?, _ action: @escaping () -> Void) {
+ if store.contextQueue == .main, Thread.isMainThread {
+ action()
+ } else {
+ store.perform(action)
+ }
+ }
+
+ func schedule(options: DispatchQueue.SchedulerOptions?, _ action: @escaping () -> Void) {
+ if store.contextQueue == .main, Thread.isMainThread {
+ action()
+ } else {
+ store.perform(action)
+ }
+ }
+ }
}
extension Scheduler {
diff --git a/EssentialApp/EssentialApp/SceneDelegate.swift b/EssentialApp/EssentialApp/SceneDelegate.swift
index 32cefcbe..c6c8f296 100644
--- a/EssentialApp/EssentialApp/SceneDelegate.swift
+++ b/EssentialApp/EssentialApp/SceneDelegate.swift
@@ -11,11 +11,17 @@ import EssentialFeed
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
- private lazy var scheduler: AnyDispatchQueueScheduler = DispatchQueue(
- label: "com.essentialdeveloper.infra.queue",
- qos: .userInitiated,
- attributes: .concurrent
- ).eraseToAnyScheduler()
+ private lazy var scheduler: AnyDispatchQueueScheduler = {
+ if let store = store as? CoreDataFeedStore {
+ return .scheduler(for: store)
+ }
+
+ return DispatchQueue(
+ label: "com.essentialdeveloper.infra.queue",
+ qos: .userInitiated,
+ attributes: .concurrent
+ ).eraseToAnyScheduler()
+ }()
private lazy var httpClient: HTTPClient = {
URLSessionHTTPClient(session: URLSession(configuration: .ephemeral))
@@ -48,11 +54,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
imageLoader: makeLocalImageLoaderWithRemoteFallback,
selection: showComments))
- convenience init(httpClient: HTTPClient, store: FeedStore & FeedImageDataStore, scheduler: AnyDispatchQueueScheduler) {
+ convenience init(httpClient: HTTPClient, store: FeedStore & FeedImageDataStore) {
self.init()
self.httpClient = httpClient
self.store = store
- self.scheduler = scheduler
}
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
diff --git a/EssentialApp/EssentialAppTests/FeedAcceptanceTests.swift b/EssentialApp/EssentialAppTests/FeedAcceptanceTests.swift
index 43958298..443ea3ae 100644
--- a/EssentialApp/EssentialAppTests/FeedAcceptanceTests.swift
+++ b/EssentialApp/EssentialAppTests/FeedAcceptanceTests.swift
@@ -9,8 +9,8 @@ import EssentialFeediOS
class FeedAcceptanceTests: XCTestCase {
- func test_onLaunch_displaysRemoteFeedWhenCustomerHasConnectivity() {
- let feed = launch(httpClient: .online(response), store: .empty)
+ func test_onLaunch_displaysRemoteFeedWhenCustomerHasConnectivity() throws {
+ let feed = try launch(httpClient: .online(response), store: .empty)
XCTAssertEqual(feed.numberOfRenderedFeedImageViews(), 2)
XCTAssertEqual(feed.renderedFeedImageData(at: 0), makeImageData0())
@@ -34,8 +34,8 @@ class FeedAcceptanceTests: XCTestCase {
XCTAssertFalse(feed.canLoadMoreFeed)
}
- func test_onLaunch_displaysCachedRemoteFeedWhenCustomerHasNoConnectivity() {
- let sharedStore = InMemoryFeedStore.empty
+ func test_onLaunch_displaysCachedRemoteFeedWhenCustomerHasNoConnectivity() throws {
+ let sharedStore = try CoreDataFeedStore.empty
let onlineFeed = launch(httpClient: .online(response), store: sharedStore)
onlineFeed.simulateFeedImageViewVisible(at: 0)
@@ -51,30 +51,30 @@ class FeedAcceptanceTests: XCTestCase {
XCTAssertEqual(offlineFeed.renderedFeedImageData(at: 2), makeImageData2())
}
- func test_onLaunch_displaysEmptyFeedWhenCustomerHasNoConnectivityAndNoCache() {
- let feed = launch(httpClient: .offline, store: .empty)
+ func test_onLaunch_displaysEmptyFeedWhenCustomerHasNoConnectivityAndNoCache() throws {
+ let feed = try launch(httpClient: .offline, store: .empty)
XCTAssertEqual(feed.numberOfRenderedFeedImageViews(), 0)
}
- func test_onEnteringBackground_deletesExpiredFeedCache() {
- let store = InMemoryFeedStore.withExpiredFeedCache
+ func test_onEnteringBackground_deletesExpiredFeedCache() throws {
+ let store = try CoreDataFeedStore.withExpiredFeedCache
enterBackground(with: store)
- XCTAssertNil(store.feedCache, "Expected to delete expired cache")
+ XCTAssertNil(try store.retrieve(), "Expected to delete expired cache")
}
- func test_onEnteringBackground_keepsNonExpiredFeedCache() {
- let store = InMemoryFeedStore.withNonExpiredFeedCache
+ func test_onEnteringBackground_keepsNonExpiredFeedCache() throws {
+ let store = try CoreDataFeedStore.withNonExpiredFeedCache
enterBackground(with: store)
- XCTAssertNotNil(store.feedCache, "Expected to keep non-expired cache")
+ XCTAssertNotNil(try store.retrieve(), "Expected to keep non-expired cache")
}
- func test_onFeedImageSelection_displaysComments() {
- let comments = showCommentsForFirstImage()
+ func test_onFeedImageSelection_displaysComments() throws {
+ let comments = try showCommentsForFirstImage()
XCTAssertEqual(comments.numberOfRenderedComments(), 1)
XCTAssertEqual(comments.commentMessage(at: 0), makeCommentMessage())
@@ -84,9 +84,9 @@ class FeedAcceptanceTests: XCTestCase {
private func launch(
httpClient: HTTPClientStub = .offline,
- store: InMemoryFeedStore = .empty
+ store: CoreDataFeedStore
) -> ListViewController {
- let sut = SceneDelegate(httpClient: httpClient, store: store, scheduler: .immediateOnMainThread)
+ let sut = SceneDelegate(httpClient: httpClient, store: store)
sut.window = UIWindow(frame: CGRect(x: 0, y: 0, width: 390, height: 1))
sut.configureWindow()
@@ -96,13 +96,13 @@ class FeedAcceptanceTests: XCTestCase {
return vc
}
- private func enterBackground(with store: InMemoryFeedStore) {
- let sut = SceneDelegate(httpClient: HTTPClientStub.offline, store: store, scheduler: .immediateOnMainThread)
+ private func enterBackground(with store: CoreDataFeedStore) {
+ let sut = SceneDelegate(httpClient: HTTPClientStub.offline, store: store)
sut.sceneWillResignActive(UIApplication.shared.connectedScenes.first!)
}
- private func showCommentsForFirstImage() -> ListViewController {
- let feed = launch(httpClient: .online(response), store: .empty)
+ private func showCommentsForFirstImage() throws -> ListViewController {
+ let feed = try launch(httpClient: .online(response), store: .empty)
feed.simulateTapOnFeedImage(at: 0)
RunLoop.current.run(until: Date())
@@ -180,3 +180,27 @@ class FeedAcceptanceTests: XCTestCase {
}
}
+
+extension CoreDataFeedStore {
+ static var empty: CoreDataFeedStore {
+ get throws {
+ try CoreDataFeedStore(storeURL: URL(fileURLWithPath: "/dev/null"), contextQueue: .main)
+ }
+ }
+
+ static var withExpiredFeedCache: CoreDataFeedStore {
+ get throws {
+ let store = try CoreDataFeedStore.empty
+ try store.insert([], timestamp: .distantPast)
+ return store
+ }
+ }
+
+ static var withNonExpiredFeedCache: CoreDataFeedStore {
+ get throws {
+ let store = try CoreDataFeedStore.empty
+ try store.insert([], timestamp: Date())
+ return store
+ }
+ }
+}
diff --git a/EssentialFeed/EssentialFeed.xcodeproj/xcshareddata/xcschemes/CI_macOS.xcscheme b/EssentialFeed/EssentialFeed.xcodeproj/xcshareddata/xcschemes/CI_macOS.xcscheme
index 5453256e..52b5a384 100644
--- a/EssentialFeed/EssentialFeed.xcodeproj/xcshareddata/xcschemes/CI_macOS.xcscheme
+++ b/EssentialFeed/EssentialFeed.xcodeproj/xcshareddata/xcschemes/CI_macOS.xcscheme
@@ -106,6 +106,12 @@
ReferencedContainer = "container:EssentialFeed.xcodeproj">
+
+
+
+
+
+
+
+
+
+
+
+
Data? {
- try performSync { context in
- Result {
- try ManagedFeedImage.data(with: url, in: context)
- }
- }
+ try ManagedFeedImage.data(with: url, in: context)
}
}
diff --git a/EssentialFeed/EssentialFeed/Feed Cache/Infrastructure/CoreData/CoreDataFeedStore+FeedStore.swift b/EssentialFeed/EssentialFeed/Feed Cache/Infrastructure/CoreData/CoreDataFeedStore+FeedStore.swift
index 88b272c7..46db2f89 100644
--- a/EssentialFeed/EssentialFeed/Feed Cache/Infrastructure/CoreData/CoreDataFeedStore+FeedStore.swift
+++ b/EssentialFeed/EssentialFeed/Feed Cache/Infrastructure/CoreData/CoreDataFeedStore+FeedStore.swift
@@ -7,32 +7,20 @@ import CoreData
extension CoreDataFeedStore: FeedStore {
public func retrieve() throws -> CachedFeed? {
- try performSync { context in
- Result {
- try ManagedCache.find(in: context).map {
- CachedFeed(feed: $0.localFeed, timestamp: $0.timestamp)
- }
- }
+ try ManagedCache.find(in: context).map {
+ CachedFeed(feed: $0.localFeed, timestamp: $0.timestamp)
}
}
public func insert(_ feed: [LocalFeedImage], timestamp: Date) throws {
- try performSync { context in
- Result {
- let managedCache = try ManagedCache.newUniqueInstance(in: context)
- managedCache.timestamp = timestamp
- managedCache.feed = ManagedFeedImage.images(from: feed, in: context)
- try context.save()
- }
- }
+ let managedCache = try ManagedCache.newUniqueInstance(in: context)
+ managedCache.timestamp = timestamp
+ managedCache.feed = ManagedFeedImage.images(from: feed, in: context)
+ try context.save()
}
public func deleteCachedFeed() throws {
- try performSync { context in
- Result {
- try ManagedCache.deleteCache(in: context)
- }
- }
+ try ManagedCache.deleteCache(in: context)
}
}
diff --git a/EssentialFeed/EssentialFeed/Feed Cache/Infrastructure/CoreData/CoreDataFeedStore.swift b/EssentialFeed/EssentialFeed/Feed Cache/Infrastructure/CoreData/CoreDataFeedStore.swift
index 64e016a0..8b935c4b 100644
--- a/EssentialFeed/EssentialFeed/Feed Cache/Infrastructure/CoreData/CoreDataFeedStore.swift
+++ b/EssentialFeed/EssentialFeed/Feed Cache/Infrastructure/CoreData/CoreDataFeedStore.swift
@@ -9,31 +9,37 @@ public final class CoreDataFeedStore {
private static let model = NSManagedObjectModel.with(name: modelName, in: Bundle(for: CoreDataFeedStore.self))
private let container: NSPersistentContainer
- private let context: NSManagedObjectContext
+ let context: NSManagedObjectContext
enum StoreError: Error {
case modelNotFound
case failedToLoadPersistentContainer(Error)
}
- public init(storeURL: URL) throws {
+ public enum ContextQueue {
+ case main
+ case background
+ }
+
+ public var contextQueue: ContextQueue {
+ context == container.viewContext ? .main : .background
+ }
+
+ public init(storeURL: URL, contextQueue: ContextQueue = .background) throws {
guard let model = CoreDataFeedStore.model else {
throw StoreError.modelNotFound
}
do {
container = try NSPersistentContainer.load(name: CoreDataFeedStore.modelName, model: model, url: storeURL)
- context = container.newBackgroundContext()
+ context = contextQueue == .main ? container.viewContext : container.newBackgroundContext()
} catch {
throw StoreError.failedToLoadPersistentContainer(error)
}
}
- func performSync(_ action: (NSManagedObjectContext) -> Result) throws -> R {
- let context = self.context
- var result: Result!
- context.performAndWait { result = action(context) }
- return try result.get()
+ public func perform(_ action: @escaping () -> Void) {
+ context.perform(action)
}
private func cleanUpReferencesToPersistentStores() {
diff --git a/EssentialFeed/EssentialFeedCacheIntegrationTests/EssentialFeedCacheIntegrationTests.swift b/EssentialFeed/EssentialFeedCacheIntegrationTests/EssentialFeedCacheIntegrationTests.swift
index 79cd64d4..5eb3f5ca 100644
--- a/EssentialFeed/EssentialFeedCacheIntegrationTests/EssentialFeedCacheIntegrationTests.swift
+++ b/EssentialFeed/EssentialFeedCacheIntegrationTests/EssentialFeedCacheIntegrationTests.swift
@@ -107,7 +107,7 @@ class EssentialFeedCacheIntegrationTests: XCTestCase {
private func makeFeedLoader(currentDate: Date = Date(), file: StaticString = #filePath, line: UInt = #line) throws -> LocalFeedLoader {
let storeURL = testSpecificStoreURL()
- let store = try CoreDataFeedStore(storeURL: storeURL)
+ let store = try CoreDataFeedStore(storeURL: storeURL, contextQueue: .main)
let sut = LocalFeedLoader(store: store, currentDate: { currentDate })
trackForMemoryLeaks(store, file: file, line: line)
trackForMemoryLeaks(sut, file: file, line: line)
@@ -116,7 +116,7 @@ class EssentialFeedCacheIntegrationTests: XCTestCase {
private func makeImageLoader(file: StaticString = #filePath, line: UInt = #line) throws -> LocalFeedImageDataLoader {
let storeURL = testSpecificStoreURL()
- let store = try CoreDataFeedStore(storeURL: storeURL)
+ let store = try CoreDataFeedStore(storeURL: storeURL, contextQueue: .main)
let sut = LocalFeedImageDataLoader(store: store)
trackForMemoryLeaks(store, file: file, line: line)
trackForMemoryLeaks(sut, file: file, line: line)
diff --git a/EssentialFeed/EssentialFeedTests/Feed Cache/CoreDataFeedImageDataStoreTests.swift b/EssentialFeed/EssentialFeedTests/Feed Cache/CoreDataFeedImageDataStoreTests.swift
index 6f1f1194..b49a2a4c 100644
--- a/EssentialFeed/EssentialFeedTests/Feed Cache/CoreDataFeedImageDataStoreTests.swift
+++ b/EssentialFeed/EssentialFeedTests/Feed Cache/CoreDataFeedImageDataStoreTests.swift
@@ -8,84 +8,93 @@ import EssentialFeed
class CoreDataFeedImageDataStoreTests: XCTestCase {
func test_retrieveImageData_deliversNotFoundWhenEmpty() throws {
- let sut = try makeSUT()
-
- expect(sut, toCompleteRetrievalWith: notFound(), for: anyURL())
+ try makeSUT { sut in
+ expect(sut, toCompleteRetrievalWith: notFound(), for: anyURL())
+ }
}
func test_retrieveImageData_deliversNotFoundWhenStoredDataURLDoesNotMatch() throws {
- let sut = try makeSUT()
- let url = URL(string: "http://a-url.com")!
- let nonMatchingURL = URL(string: "http://another-url.com")!
-
- insert(anyData(), for: url, into: sut)
-
- expect(sut, toCompleteRetrievalWith: notFound(), for: nonMatchingURL)
+ try makeSUT { sut in
+ let url = URL(string: "http://a-url.com")!
+ let nonMatchingURL = URL(string: "http://another-url.com")!
+
+ insert(anyData(), for: url, into: sut)
+
+ expect(sut, toCompleteRetrievalWith: notFound(), for: nonMatchingURL)
+ }
}
func test_retrieveImageData_deliversFoundDataWhenThereIsAStoredImageDataMatchingURL() throws {
- let sut = try makeSUT()
- let storedData = anyData()
- let matchingURL = URL(string: "http://a-url.com")!
-
- insert(storedData, for: matchingURL, into: sut)
-
- expect(sut, toCompleteRetrievalWith: found(storedData), for: matchingURL)
+ try makeSUT { sut in
+ let storedData = anyData()
+ let matchingURL = URL(string: "http://a-url.com")!
+
+ insert(storedData, for: matchingURL, into: sut)
+
+ expect(sut, toCompleteRetrievalWith: found(storedData), for: matchingURL)
+ }
}
func test_retrieveImageData_deliversLastInsertedValue() throws {
- let sut = try makeSUT()
- let firstStoredData = Data("first".utf8)
- let lastStoredData = Data("last".utf8)
- let url = URL(string: "http://a-url.com")!
-
- insert(firstStoredData, for: url, into: sut)
- insert(lastStoredData, for: url, into: sut)
-
- expect(sut, toCompleteRetrievalWith: found(lastStoredData), for: url)
+ try makeSUT { sut in
+ let firstStoredData = Data("first".utf8)
+ let lastStoredData = Data("last".utf8)
+ let url = URL(string: "http://a-url.com")!
+
+ insert(firstStoredData, for: url, into: sut)
+ insert(lastStoredData, for: url, into: sut)
+
+ expect(sut, toCompleteRetrievalWith: found(lastStoredData), for: url)
+ }
}
// - MARK: Helpers
- private func makeSUT(file: StaticString = #filePath, line: UInt = #line) throws -> CoreDataFeedStore {
+ private func makeSUT(_ test: @escaping (CoreDataFeedStore) -> Void, file: StaticString = #filePath, line: UInt = #line) throws {
let storeURL = URL(fileURLWithPath: "/dev/null")
let sut = try CoreDataFeedStore(storeURL: storeURL)
trackForMemoryLeaks(sut, file: file, line: line)
- return sut
- }
-
- private func notFound() -> Result {
- return .success(.none)
- }
-
- private func found(_ data: Data) -> Result {
- return .success(data)
- }
-
- private func localImage(url: URL) -> LocalFeedImage {
- return LocalFeedImage(id: UUID(), description: "any", location: "any", url: url)
+
+ let exp = expectation(description: "wait for operation")
+ sut.perform {
+ test(sut)
+ exp.fulfill()
+ }
+ wait(for: [exp], timeout: 0.1)
}
- private func expect(_ sut: CoreDataFeedStore, toCompleteRetrievalWith expectedResult: Result, for url: URL, file: StaticString = #filePath, line: UInt = #line) {
- let receivedResult = Result { try sut.retrieve(dataForURL: url) }
+}
- switch (receivedResult, expectedResult) {
- case let (.success( receivedData), .success(expectedData)):
- XCTAssertEqual(receivedData, expectedData, file: file, line: line)
-
- default:
- XCTFail("Expected \(expectedResult), got \(receivedResult) instead", file: file, line: line)
- }
+private func notFound() -> Result {
+ return .success(.none)
+}
+
+private func found(_ data: Data) -> Result {
+ return .success(data)
+}
+
+private func localImage(url: URL) -> LocalFeedImage {
+ return LocalFeedImage(id: UUID(), description: "any", location: "any", url: url)
+}
+
+private func expect(_ sut: CoreDataFeedStore, toCompleteRetrievalWith expectedResult: Result, for url: URL, file: StaticString = #filePath, line: UInt = #line) {
+ let receivedResult = Result { try sut.retrieve(dataForURL: url) }
+
+ switch (receivedResult, expectedResult) {
+ case let (.success( receivedData), .success(expectedData)):
+ XCTAssertEqual(receivedData, expectedData, file: file, line: line)
+
+ default:
+ XCTFail("Expected \(expectedResult), got \(receivedResult) instead", file: file, line: line)
}
-
- private func insert(_ data: Data, for url: URL, into sut: CoreDataFeedStore, file: StaticString = #filePath, line: UInt = #line) {
- do {
- let image = localImage(url: url)
- try sut.insert([image], timestamp: Date())
- try sut.insert(data, for: url)
- } catch {
- XCTFail("Failed to insert \(data) with error \(error)", file: file, line: line)
- }
+}
+
+private func insert(_ data: Data, for url: URL, into sut: CoreDataFeedStore, file: StaticString = #filePath, line: UInt = #line) {
+ do {
+ let image = localImage(url: url)
+ try sut.insert([image], timestamp: Date())
+ try sut.insert(data, for: url)
+ } catch {
+ XCTFail("Failed to insert \(data) with error \(error)", file: file, line: line)
}
-
}
diff --git a/EssentialFeed/EssentialFeedTests/Feed Cache/CoreDataFeedStoreTests.swift b/EssentialFeed/EssentialFeedTests/Feed Cache/CoreDataFeedStoreTests.swift
index f048a963..643b6933 100644
--- a/EssentialFeed/EssentialFeedTests/Feed Cache/CoreDataFeedStoreTests.swift
+++ b/EssentialFeed/EssentialFeedTests/Feed Cache/CoreDataFeedStoreTests.swift
@@ -8,78 +8,84 @@ import EssentialFeed
class CoreDataFeedStoreTests: XCTestCase, FeedStoreSpecs {
func test_retrieve_deliversEmptyOnEmptyCache() throws {
- let sut = try makeSUT()
-
- assertThatRetrieveDeliversEmptyOnEmptyCache(on: sut)
+ try makeSUT { sut in
+ self.assertThatRetrieveDeliversEmptyOnEmptyCache(on: sut)
+ }
}
func test_retrieve_hasNoSideEffectsOnEmptyCache() throws {
- let sut = try makeSUT()
-
- assertThatRetrieveHasNoSideEffectsOnEmptyCache(on: sut)
+ try makeSUT { sut in
+ self.assertThatRetrieveHasNoSideEffectsOnEmptyCache(on: sut)
+ }
}
func test_retrieve_deliversFoundValuesOnNonEmptyCache() throws {
- let sut = try makeSUT()
-
- assertThatRetrieveDeliversFoundValuesOnNonEmptyCache(on: sut)
+ try makeSUT { sut in
+ self.assertThatRetrieveDeliversFoundValuesOnNonEmptyCache(on: sut)
+ }
}
func test_retrieve_hasNoSideEffectsOnNonEmptyCache() throws {
- let sut = try makeSUT()
-
- assertThatRetrieveHasNoSideEffectsOnNonEmptyCache(on: sut)
+ try makeSUT { sut in
+ self.assertThatRetrieveHasNoSideEffectsOnNonEmptyCache(on: sut)
+ }
}
func test_insert_deliversNoErrorOnEmptyCache() throws {
- let sut = try makeSUT()
-
- assertThatInsertDeliversNoErrorOnEmptyCache(on: sut)
+ try makeSUT { sut in
+ self.assertThatInsertDeliversNoErrorOnEmptyCache(on: sut)
+ }
}
func test_insert_deliversNoErrorOnNonEmptyCache() throws {
- let sut = try makeSUT()
-
- assertThatInsertDeliversNoErrorOnNonEmptyCache(on: sut)
+ try makeSUT { sut in
+ self.assertThatInsertDeliversNoErrorOnNonEmptyCache(on: sut)
+ }
}
func test_insert_overridesPreviouslyInsertedCacheValues() throws {
- let sut = try makeSUT()
-
- assertThatInsertOverridesPreviouslyInsertedCacheValues(on: sut)
+ try makeSUT { sut in
+ self.assertThatInsertOverridesPreviouslyInsertedCacheValues(on: sut)
+ }
}
func test_delete_deliversNoErrorOnEmptyCache() throws {
- let sut = try makeSUT()
-
- assertThatDeleteDeliversNoErrorOnEmptyCache(on: sut)
+ try makeSUT { sut in
+ self.assertThatDeleteDeliversNoErrorOnEmptyCache(on: sut)
+ }
}
func test_delete_hasNoSideEffectsOnEmptyCache() throws {
- let sut = try makeSUT()
-
- assertThatDeleteHasNoSideEffectsOnEmptyCache(on: sut)
+ try makeSUT { sut in
+ self.assertThatDeleteHasNoSideEffectsOnEmptyCache(on: sut)
+ }
}
func test_delete_deliversNoErrorOnNonEmptyCache() throws {
- let sut = try makeSUT()
-
- assertThatDeleteDeliversNoErrorOnNonEmptyCache(on: sut)
+ try makeSUT { sut in
+ self.assertThatDeleteDeliversNoErrorOnNonEmptyCache(on: sut)
+ }
}
func test_delete_emptiesPreviouslyInsertedCache() throws {
- let sut = try makeSUT()
-
- assertThatDeleteEmptiesPreviouslyInsertedCache(on: sut)
+ try makeSUT { sut in
+ self.assertThatDeleteEmptiesPreviouslyInsertedCache(on: sut)
+ }
}
// - MARK: Helpers
- private func makeSUT(file: StaticString = #filePath, line: UInt = #line) throws -> FeedStore {
+ private func makeSUT(_ test: @escaping (CoreDataFeedStore) -> Void, file: StaticString = #filePath, line: UInt = #line) throws {
let storeURL = URL(fileURLWithPath: "/dev/null")
let sut = try CoreDataFeedStore(storeURL: storeURL)
trackForMemoryLeaks(sut, file: file, line: line)
- return sut
+
+ let exp = expectation(description: "wait for operation")
+ sut.perform {
+ test(sut)
+ exp.fulfill()
+ }
+ wait(for: [exp], timeout: 0.1)
}
}