Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions EssentialApp/EssentialApp/CombineHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ extension AnyDispatchQueueScheduler {
CoreDataFeedStoreScheduler(store: store).eraseToAnyScheduler()
}

@MainActor
private struct CoreDataFeedStoreScheduler: Scheduler {
let store: CoreDataFeedStore

Expand All @@ -239,7 +240,9 @@ extension AnyDispatchQueueScheduler {
action()
} else {
nonisolated(unsafe) let uncheckedAction = action
store.perform { uncheckedAction() }
Task.immediate {
await store.perform { uncheckedAction() }
}
}
return AnyCancellable {}
}
Expand All @@ -249,7 +252,9 @@ extension AnyDispatchQueueScheduler {
action()
} else {
nonisolated(unsafe) let uncheckedAction = action
store.perform { uncheckedAction() }
Task.immediate {
await store.perform { uncheckedAction() }
}
}
}

Expand All @@ -258,7 +263,9 @@ extension AnyDispatchQueueScheduler {
action()
} else {
nonisolated(unsafe) let uncheckedAction = action
store.perform { uncheckedAction() }
Task.immediate {
await store.perform { uncheckedAction() }
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ public final class CoreDataFeedStore: Sendable {
}
}

public func perform(_ action: @Sendable @escaping () -> Void) {
context.perform(action)
public func perform<T>(_ action: @escaping @Sendable () throws -> T) async rethrows -> T {
try await context.perform(action)
}

private func cleanUpReferencesToPersistentStores() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,42 @@ import EssentialFeed
@MainActor
class CoreDataFeedImageDataStoreTests: XCTestCase, FeedImageDataStoreSpecs {

func test_retrieveImageData_deliversNotFoundWhenEmpty() throws {
try makeSUT { sut, imageDataURL in
func test_retrieveImageData_deliversNotFoundWhenEmpty() async throws {
try await makeSUT { sut, imageDataURL in
assertThatRetrieveImageDataDeliversNotFoundOnEmptyCache(on: sut, imageDataURL: imageDataURL)
}
}

func test_retrieveImageData_deliversNotFoundWhenStoredDataURLDoesNotMatch() throws {
try makeSUT { sut, imageDataURL in
func test_retrieveImageData_deliversNotFoundWhenStoredDataURLDoesNotMatch() async throws {
try await makeSUT { sut, imageDataURL in
assertThatRetrieveImageDataDeliversNotFoundWhenStoredDataURLDoesNotMatch(on: sut, imageDataURL: imageDataURL)
}
}

func test_retrieveImageData_deliversFoundDataWhenThereIsAStoredImageDataMatchingURL() throws {
try makeSUT { sut, imageDataURL in
func test_retrieveImageData_deliversFoundDataWhenThereIsAStoredImageDataMatchingURL() async throws {
try await makeSUT { sut, imageDataURL in
assertThatRetrieveImageDataDeliversFoundDataWhenThereIsAStoredImageDataMatchingURL(on: sut, imageDataURL: imageDataURL)
}
}

func test_retrieveImageData_deliversLastInsertedValue() throws {
try makeSUT { sut, imageDataURL in
func test_retrieveImageData_deliversLastInsertedValue() async throws {
try await makeSUT { sut, imageDataURL in
assertThatRetrieveImageDataDeliversLastInsertedValueForURL(on: sut, imageDataURL: imageDataURL)
}
}

// - MARK: Helpers

private func makeSUT(_ test: @Sendable @escaping (CoreDataFeedStore, URL) -> Void, file: StaticString = #filePath, line: UInt = #line) throws {
private func makeSUT(_ test: @Sendable @escaping (CoreDataFeedStore, URL) -> Void, file: StaticString = #filePath, line: UInt = #line) async throws {
let storeURL = URL(fileURLWithPath: "/dev/null")
let sut = try CoreDataFeedStore(storeURL: storeURL)
trackForMemoryLeaks(sut, file: file, line: line)

let exp = expectation(description: "wait for operation")
sut.perform {
await sut.perform {
let imageDataURL = URL(string: "http://a-url.com")!
insertFeedImage(with: imageDataURL, into: sut, file: file, line: line)
test(sut, imageDataURL)
exp.fulfill()
}
wait(for: [exp], timeout: 0.1)
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,85 +8,82 @@ import EssentialFeed
@MainActor
class CoreDataFeedStoreTests: XCTestCase, FeedStoreSpecs {

func test_retrieve_deliversEmptyOnEmptyCache() throws {
try makeSUT { sut in
func test_retrieve_deliversEmptyOnEmptyCache() async throws {
try await makeSUT { sut in
assertThatRetrieveDeliversEmptyOnEmptyCache(on: sut)
}
}

func test_retrieve_hasNoSideEffectsOnEmptyCache() throws {
try makeSUT { sut in
func test_retrieve_hasNoSideEffectsOnEmptyCache() async throws {
try await makeSUT { sut in
assertThatRetrieveHasNoSideEffectsOnEmptyCache(on: sut)
}
}

func test_retrieve_deliversFoundValuesOnNonEmptyCache() throws {
try makeSUT { sut in
func test_retrieve_deliversFoundValuesOnNonEmptyCache() async throws {
try await makeSUT { sut in
assertThatRetrieveDeliversFoundValuesOnNonEmptyCache(on: sut)
}
}

func test_retrieve_hasNoSideEffectsOnNonEmptyCache() throws {
try makeSUT { sut in
func test_retrieve_hasNoSideEffectsOnNonEmptyCache() async throws {
try await makeSUT { sut in
assertThatRetrieveHasNoSideEffectsOnNonEmptyCache(on: sut)
}
}

func test_insert_deliversNoErrorOnEmptyCache() throws {
try makeSUT { sut in
func test_insert_deliversNoErrorOnEmptyCache() async throws {
try await makeSUT { sut in
assertThatInsertDeliversNoErrorOnEmptyCache(on: sut)
}
}

func test_insert_deliversNoErrorOnNonEmptyCache() throws {
try makeSUT { sut in
func test_insert_deliversNoErrorOnNonEmptyCache() async throws {
try await makeSUT { sut in
assertThatInsertDeliversNoErrorOnNonEmptyCache(on: sut)
}
}

func test_insert_overridesPreviouslyInsertedCacheValues() throws {
try makeSUT { sut in
func test_insert_overridesPreviouslyInsertedCacheValues() async throws {
try await makeSUT { sut in
assertThatInsertOverridesPreviouslyInsertedCacheValues(on: sut)
}
}

func test_delete_deliversNoErrorOnEmptyCache() throws {
try makeSUT { sut in
func test_delete_deliversNoErrorOnEmptyCache() async throws {
try await makeSUT { sut in
assertThatDeleteDeliversNoErrorOnEmptyCache(on: sut)
}
}

func test_delete_hasNoSideEffectsOnEmptyCache() throws {
try makeSUT { sut in
func test_delete_hasNoSideEffectsOnEmptyCache() async throws {
try await makeSUT { sut in
assertThatDeleteHasNoSideEffectsOnEmptyCache(on: sut)
}
}

func test_delete_deliversNoErrorOnNonEmptyCache() throws {
try makeSUT { sut in
func test_delete_deliversNoErrorOnNonEmptyCache() async throws {
try await makeSUT { sut in
assertThatDeleteDeliversNoErrorOnNonEmptyCache(on: sut)
}
}

func test_delete_emptiesPreviouslyInsertedCache() throws {
try makeSUT { sut in
func test_delete_emptiesPreviouslyInsertedCache() async throws {
try await makeSUT { sut in
assertThatDeleteEmptiesPreviouslyInsertedCache(on: sut)
}
}

// - MARK: Helpers

private func makeSUT(_ test: @Sendable @escaping (CoreDataFeedStore) -> Void, file: StaticString = #filePath, line: UInt = #line) throws {
private func makeSUT(_ test: @Sendable @escaping (CoreDataFeedStore) -> Void, file: StaticString = #filePath, line: UInt = #line) async throws {
let storeURL = URL(fileURLWithPath: "/dev/null")
let sut = try CoreDataFeedStore(storeURL: storeURL)
trackForMemoryLeaks(sut, file: file, line: line)

let exp = expectation(description: "wait for operation")
sut.perform {
await sut.perform {
test(sut)
exp.fulfill()
}
wait(for: [exp], timeout: 0.1)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import Foundation

protocol FeedImageDataStoreSpecs {
func test_retrieveImageData_deliversNotFoundWhenEmpty() throws
func test_retrieveImageData_deliversNotFoundWhenStoredDataURLDoesNotMatch() throws
func test_retrieveImageData_deliversFoundDataWhenThereIsAStoredImageDataMatchingURL() throws
func test_retrieveImageData_deliversLastInsertedValue() throws
func test_retrieveImageData_deliversNotFoundWhenEmpty() async throws
func test_retrieveImageData_deliversNotFoundWhenStoredDataURLDoesNotMatch() async throws
func test_retrieveImageData_deliversFoundDataWhenThereIsAStoredImageDataMatchingURL() async throws
func test_retrieveImageData_deliversLastInsertedValue() async throws
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,34 @@
import Foundation

protocol FeedStoreSpecs {
func test_retrieve_deliversEmptyOnEmptyCache() throws
func test_retrieve_hasNoSideEffectsOnEmptyCache() throws
func test_retrieve_deliversFoundValuesOnNonEmptyCache() throws
func test_retrieve_hasNoSideEffectsOnNonEmptyCache() throws
func test_retrieve_deliversEmptyOnEmptyCache() async throws
func test_retrieve_hasNoSideEffectsOnEmptyCache() async throws
func test_retrieve_deliversFoundValuesOnNonEmptyCache() async throws
func test_retrieve_hasNoSideEffectsOnNonEmptyCache() async throws

func test_insert_deliversNoErrorOnEmptyCache() throws
func test_insert_deliversNoErrorOnNonEmptyCache() throws
func test_insert_overridesPreviouslyInsertedCacheValues() throws
func test_insert_deliversNoErrorOnEmptyCache() async throws
func test_insert_deliversNoErrorOnNonEmptyCache() async throws
func test_insert_overridesPreviouslyInsertedCacheValues() async throws

func test_delete_deliversNoErrorOnEmptyCache() throws
func test_delete_hasNoSideEffectsOnEmptyCache() throws
func test_delete_deliversNoErrorOnNonEmptyCache() throws
func test_delete_emptiesPreviouslyInsertedCache() throws
func test_delete_deliversNoErrorOnEmptyCache() async throws
func test_delete_hasNoSideEffectsOnEmptyCache() async throws
func test_delete_deliversNoErrorOnNonEmptyCache() async throws
func test_delete_emptiesPreviouslyInsertedCache() async throws
}

protocol FailableRetrieveFeedStoreSpecs: FeedStoreSpecs {
func test_retrieve_deliversFailureOnRetrievalError() throws
func test_retrieve_hasNoSideEffectsOnFailure() throws
func test_retrieve_deliversFailureOnRetrievalError() async throws
func test_retrieve_hasNoSideEffectsOnFailure() async throws
}

protocol FailableInsertFeedStoreSpecs: FeedStoreSpecs {
func test_insert_deliversErrorOnInsertionError() throws
func test_insert_hasNoSideEffectsOnInsertionError() throws
func test_insert_deliversErrorOnInsertionError() async throws
func test_insert_hasNoSideEffectsOnInsertionError() async throws
}

protocol FailableDeleteFeedStoreSpecs: FeedStoreSpecs {
func test_delete_deliversErrorOnDeletionError() throws
func test_delete_hasNoSideEffectsOnDeletionError() throws
func test_delete_deliversErrorOnDeletionError() async throws
func test_delete_hasNoSideEffectsOnDeletionError() async throws
}

typealias FailableFeedStoreSpecs = FailableRetrieveFeedStoreSpecs & FailableInsertFeedStoreSpecs & FailableDeleteFeedStoreSpecs