From bc4c32829c4af6d9f170022e7153d67f7350ba95 Mon Sep 17 00:00:00 2001 From: Jay Herron Date: Wed, 15 Apr 2026 00:39:22 -0600 Subject: [PATCH] test: Converts tests to Swift Testing --- .../DataLoaderAbuseTests.swift | 24 +- .../DataLoaderTests.swift | 167 +++++------ .../DataLoaderAbuseTests.swift | 61 ++-- .../DataLoaderAsyncTests.swift | 165 +++++------ Tests/DataLoaderTests/DataLoaderTests.swift | 264 ++++++++---------- 5 files changed, 310 insertions(+), 371 deletions(-) diff --git a/Tests/AsyncDataLoaderTests/DataLoaderAbuseTests.swift b/Tests/AsyncDataLoaderTests/DataLoaderAbuseTests.swift index 57a50b2..887054a 100644 --- a/Tests/AsyncDataLoaderTests/DataLoaderAbuseTests.swift +++ b/Tests/AsyncDataLoaderTests/DataLoaderAbuseTests.swift @@ -1,10 +1,10 @@ -import XCTest +import Testing @testable import AsyncDataLoader /// Provides descriptive error messages for API abuse -class DataLoaderAbuseTests: XCTestCase { - func testFuntionWithNoValues() async throws { +struct DataLoaderAbuseTests { + @Test func funtionWithNoValues() async throws { let identityLoader = DataLoader( options: DataLoaderOptions(batchingEnabled: false) ) { _ in @@ -21,10 +21,10 @@ class DataLoaderAbuseTests: XCTestCase { didFailWithError = error } - XCTAssertNotNil(didFailWithError) + #expect(didFailWithError != nil) } - func testBatchFuntionMustPromiseAnArrayOfCorrectLength() async { + @Test func batchFuntionMustPromiseAnArrayOfCorrectLength() async { let identityLoader = DataLoader { _ in [] } @@ -39,10 +39,10 @@ class DataLoaderAbuseTests: XCTestCase { didFailWithError = error } - XCTAssertNotNil(didFailWithError) + #expect(didFailWithError != nil) } - func testBatchFuntionWithSomeValues() async throws { + @Test func batchFuntionWithSomeValues() async throws { let identityLoader = DataLoader { keys in var results = [DataLoaderValue]() @@ -68,14 +68,14 @@ class DataLoaderAbuseTests: XCTestCase { didFailWithError = error } - XCTAssertNotNil(didFailWithError) + #expect(didFailWithError != nil) let value = try await value1 - XCTAssertTrue(value == 1) + #expect(value == 1) } - func testFuntionWithSomeValues() async throws { + @Test func funtionWithSomeValues() async throws { let identityLoader = DataLoader( options: DataLoaderOptions(batchingEnabled: false) ) { keys in @@ -103,11 +103,11 @@ class DataLoaderAbuseTests: XCTestCase { didFailWithError = error } - XCTAssertNotNil(didFailWithError) + #expect(didFailWithError != nil) let value = try await value1 - XCTAssertTrue(value == 1) + #expect(value == 1) } } diff --git a/Tests/AsyncDataLoaderTests/DataLoaderTests.swift b/Tests/AsyncDataLoaderTests/DataLoaderTests.swift index 64e13d1..fee8c5e 100644 --- a/Tests/AsyncDataLoaderTests/DataLoaderTests.swift +++ b/Tests/AsyncDataLoaderTests/DataLoaderTests.swift @@ -1,4 +1,5 @@ -import XCTest +import Foundation +import Testing @testable import AsyncDataLoader @@ -24,9 +25,9 @@ actor Concurrent { /// The `try await Task.sleep(nanoseconds: 2_000_000)` introduces a small delay to simulate /// asynchronous behavior and ensure that concurrent requests (`value1`, `value2`...) /// are grouped into a single batch for processing, as intended by the batching settings. -final class DataLoaderTests: XCTestCase { +struct DataLoaderTests { /// Builds a really really simple data loader' - func testReallyReallySimpleDataLoader() async throws { + @Test func reallyReallySimpleDataLoader() async throws { let identityLoader = DataLoader( options: DataLoaderOptions(batchingEnabled: false) ) { keys in @@ -35,26 +36,26 @@ final class DataLoaderTests: XCTestCase { let value = try await identityLoader.load(key: 1) - XCTAssertEqual(value, 1) + #expect(value == 1) } /// Supports loading multiple keys in one call - func testLoadingMultipleKeys() async throws { + @Test func loadingMultipleKeys() async throws { let identityLoader = DataLoader { keys in keys.map { DataLoaderValue.success($0) } } let values = try await identityLoader.loadMany(keys: [1, 2]) - XCTAssertEqual(values, [1, 2]) + #expect(values == [1, 2]) let empty = try await identityLoader.loadMany(keys: []) - XCTAssertTrue(empty.isEmpty) + #expect(empty.isEmpty) } /// Batches multiple requests - func testMultipleRequests() async throws { + @Test func multipleRequests() async throws { let loadCalls = Concurrent<[[Int]]>([]) let identityLoader = DataLoader( @@ -81,21 +82,21 @@ final class DataLoaderTests: XCTestCase { didFailWithError = error } - XCTAssertNil(didFailWithError) + #expect(didFailWithError == nil) let result1 = try await value1 let result2 = try await value2 - XCTAssertEqual(result1, 1) - XCTAssertEqual(result2, 2) + #expect(result1 == 1) + #expect(result2 == 2) let calls = await loadCalls.wrappedValue - XCTAssertEqual(calls.map { $0.sorted() }, [[1, 2]]) + #expect(calls.map { $0.sorted() } == [[1, 2]]) } /// Batches multiple requests with max batch sizes - func testMultipleRequestsWithMaxBatchSize() async throws { + @Test func multipleRequestsWithMaxBatchSize() async throws { let loadCalls = Concurrent<[[Int]]>([]) let identityLoader = DataLoader( @@ -124,24 +125,24 @@ final class DataLoaderTests: XCTestCase { didFailWithError = error } - XCTAssertNil(didFailWithError) + #expect(didFailWithError == nil) let result1 = try await value1 let result2 = try await value2 let result3 = try await value3 - XCTAssertEqual(result1, 1) - XCTAssertEqual(result2, 2) - XCTAssertEqual(result3, 3) + #expect(result1 == 1) + #expect(result2 == 2) + #expect(result3 == 3) let calls = await loadCalls.wrappedValue - XCTAssertEqual(calls.first?.count, 2) - XCTAssertEqual(calls.last?.count, 1) + #expect(calls.first?.count == 2) + #expect(calls.last?.count == 1) } /// Coalesces identical requests - func testCoalescesIdenticalRequests() async throws { + @Test func coalescesIdenticalRequests() async throws { let loadCalls = Concurrent<[[Int]]>([]) let identityLoader = DataLoader( @@ -165,21 +166,21 @@ final class DataLoaderTests: XCTestCase { didFailWithError = error } - XCTAssertNil(didFailWithError) + #expect(didFailWithError == nil) let result1 = try await value1 let result2 = try await value2 - XCTAssertTrue(result1 == 1) - XCTAssertTrue(result2 == 1) + #expect(result1 == 1) + #expect(result2 == 1) let calls = await loadCalls.wrappedValue - XCTAssertTrue(calls.map { $0.sorted() } == [[1]]) + #expect(calls.map { $0.sorted() } == [[1]]) } /// Caches repeated requests - func testCachesRepeatedRequests() async throws { + @Test func cachesRepeatedRequests() async throws { let loadCalls = Concurrent<[[String]]>([]) let identityLoader = DataLoader( @@ -203,17 +204,17 @@ final class DataLoaderTests: XCTestCase { didFailWithError = error } - XCTAssertNil(didFailWithError) + #expect(didFailWithError == nil) let result1 = try await value1 let result2 = try await value2 - XCTAssertTrue(result1 == "A") - XCTAssertTrue(result2 == "B") + #expect(result1 == "A") + #expect(result2 == "B") let calls = await loadCalls.wrappedValue - XCTAssertTrue(calls.map { $0.sorted() } == [["A", "B"]]) + #expect(calls.map { $0.sorted() } == [["A", "B"]]) async let value3 = identityLoader.load(key: "A") async let value4 = identityLoader.load(key: "C") @@ -228,17 +229,17 @@ final class DataLoaderTests: XCTestCase { didFailWithError2 = error } - XCTAssertNil(didFailWithError2) + #expect(didFailWithError2 == nil) let result3 = try await value3 let result4 = try await value4 - XCTAssertTrue(result3 == "A") - XCTAssertTrue(result4 == "C") + #expect(result3 == "A") + #expect(result4 == "C") let calls2 = await loadCalls.wrappedValue - XCTAssertTrue(calls2.map { $0.sorted() } == [["A", "B"], ["C"]]) + #expect(calls2.map { $0.sorted() } == [["A", "B"], ["C"]]) async let value5 = identityLoader.load(key: "A") async let value6 = identityLoader.load(key: "B") @@ -254,23 +255,23 @@ final class DataLoaderTests: XCTestCase { didFailWithError3 = error } - XCTAssertNil(didFailWithError3) + #expect(didFailWithError3 == nil) let result5 = try await value5 let result6 = try await value6 let result7 = try await value7 - XCTAssertTrue(result5 == "A") - XCTAssertTrue(result6 == "B") - XCTAssertTrue(result7 == "C") + #expect(result5 == "A") + #expect(result6 == "B") + #expect(result7 == "C") let calls3 = await loadCalls.wrappedValue - XCTAssertTrue(calls3.map { $0.sorted() } == [["A", "B"], ["C"]]) + #expect(calls3.map { $0.sorted() } == [["A", "B"], ["C"]]) } /// Clears single value in loader - func testClearSingleValueLoader() async throws { + @Test func clearSingleValueLoader() async throws { let loadCalls = Concurrent<[[String]]>([]) let identityLoader = DataLoader( @@ -294,17 +295,17 @@ final class DataLoaderTests: XCTestCase { didFailWithError = error } - XCTAssertNil(didFailWithError) + #expect(didFailWithError == nil) let result1 = try await value1 let result2 = try await value2 - XCTAssertTrue(result1 == "A") - XCTAssertTrue(result2 == "B") + #expect(result1 == "A") + #expect(result2 == "B") let calls = await loadCalls.wrappedValue - XCTAssertTrue(calls.map { $0.sorted() } == [["A", "B"]]) + #expect(calls.map { $0.sorted() } == [["A", "B"]]) await identityLoader.clear(key: "A") @@ -321,21 +322,21 @@ final class DataLoaderTests: XCTestCase { didFailWithError2 = error } - XCTAssertNil(didFailWithError2) + #expect(didFailWithError2 == nil) let result3 = try await value3 let result4 = try await value4 - XCTAssertTrue(result3 == "A") - XCTAssertTrue(result4 == "B") + #expect(result3 == "A") + #expect(result4 == "B") let calls2 = await loadCalls.wrappedValue - XCTAssertTrue(calls2.map { $0.sorted() } == [["A", "B"], ["A"]]) + #expect(calls2.map { $0.sorted() } == [["A", "B"], ["A"]]) } /// Clears all values in loader - func testClearsAllValuesInLoader() async throws { + @Test func clearsAllValuesInLoader() async throws { let loadCalls = Concurrent<[[String]]>([]) let identityLoader = DataLoader( @@ -359,17 +360,17 @@ final class DataLoaderTests: XCTestCase { didFailWithError = error } - XCTAssertNil(didFailWithError) + #expect(didFailWithError == nil) let result1 = try await value1 let result2 = try await value2 - XCTAssertTrue(result1 == "A") - XCTAssertTrue(result2 == "B") + #expect(result1 == "A") + #expect(result2 == "B") let calls = await loadCalls.wrappedValue - XCTAssertTrue(calls.map { $0.sorted() } == [["A", "B"]]) + #expect(calls.map { $0.sorted() } == [["A", "B"]]) await identityLoader.clearAll() @@ -386,21 +387,21 @@ final class DataLoaderTests: XCTestCase { didFailWithError2 = error } - XCTAssertNil(didFailWithError2) + #expect(didFailWithError2 == nil) let result3 = try await value3 let result4 = try await value4 - XCTAssertTrue(result3 == "A") - XCTAssertTrue(result4 == "B") + #expect(result3 == "A") + #expect(result4 == "B") let calls2 = await loadCalls.wrappedValue - XCTAssertTrue(calls2.map { $0.sorted() } == [["A", "B"], ["A", "B"]]) + #expect(calls2.map { $0.sorted() } == [["A", "B"], ["A", "B"]]) } /// Allows priming the cache - func testAllowsPrimingTheCache() async throws { + @Test func allowsPrimingTheCache() async throws { let loadCalls = Concurrent<[[String]]>([]) let identityLoader = DataLoader( @@ -426,21 +427,21 @@ final class DataLoaderTests: XCTestCase { didFailWithError = error } - XCTAssertNil(didFailWithError) + #expect(didFailWithError == nil) let result1 = try await value1 let result2 = try await value2 - XCTAssertTrue(result1 == "A") - XCTAssertTrue(result2 == "B") + #expect(result1 == "A") + #expect(result2 == "B") let calls = await loadCalls.wrappedValue - XCTAssertTrue(calls.map { $0.sorted() } == [["B"]]) + #expect(calls.map { $0.sorted() } == [["B"]]) } /// Does not prime keys that already exist - func testDoesNotPrimeKeysThatAlreadyExist() async throws { + @Test func doesNotPrimeKeysThatAlreadyExist() async throws { let loadCalls = Concurrent<[[String]]>([]) let identityLoader = DataLoader( @@ -466,13 +467,13 @@ final class DataLoaderTests: XCTestCase { didFailWithError = error } - XCTAssertNil(didFailWithError) + #expect(didFailWithError == nil) let result1 = try await value1 let result2 = try await value2 - XCTAssertTrue(result1 == "X") - XCTAssertTrue(result2 == "B") + #expect(result1 == "X") + #expect(result2 == "B") try await identityLoader.prime(key: "A", value: "Y") try await identityLoader.prime(key: "B", value: "Y") @@ -490,21 +491,21 @@ final class DataLoaderTests: XCTestCase { didFailWithError2 = error } - XCTAssertNil(didFailWithError2) + #expect(didFailWithError2 == nil) let result3 = try await value3 let result4 = try await value4 - XCTAssertTrue(result3 == "X") - XCTAssertTrue(result4 == "B") + #expect(result3 == "X") + #expect(result4 == "B") let calls = await loadCalls.wrappedValue - XCTAssertTrue(calls.map { $0.sorted() } == [["B"]]) + #expect(calls.map { $0.sorted() } == [["B"]]) } /// Allows forcefully priming the cache - func testAllowsForcefullyPrimingTheCache() async throws { + @Test func allowsForcefullyPrimingTheCache() async throws { let loadCalls = Concurrent<[[String]]>([]) let identityLoader = DataLoader( @@ -530,13 +531,13 @@ final class DataLoaderTests: XCTestCase { didFailWithError = error } - XCTAssertNil(didFailWithError) + #expect(didFailWithError == nil) let result1 = try await value1 let result2 = try await value2 - XCTAssertTrue(result1 == "X") - XCTAssertTrue(result2 == "B") + #expect(result1 == "X") + #expect(result2 == "B") try await identityLoader.clear(key: "A").prime(key: "A", value: "Y") try await identityLoader.clear(key: "B").prime(key: "B", value: "Y") @@ -554,20 +555,20 @@ final class DataLoaderTests: XCTestCase { didFailWithError2 = error } - XCTAssertNil(didFailWithError2) + #expect(didFailWithError2 == nil) let result3 = try await value3 let result4 = try await value4 - XCTAssertTrue(result3 == "Y") - XCTAssertTrue(result4 == "Y") + #expect(result3 == "Y") + #expect(result4 == "Y") let calls = await loadCalls.wrappedValue - XCTAssertTrue(calls.map { $0.sorted() } == [["B"]]) + #expect(calls.map { $0.sorted() } == [["B"]]) } - func testAutoExecute() async throws { + @Test func autoExecute() async throws { let identityLoader = DataLoader( options: DataLoaderOptions(executionPeriod: sleepConstant) ) { keys in @@ -581,10 +582,10 @@ final class DataLoaderTests: XCTestCase { let result = try await value - XCTAssertNotNil(result) + #expect(result == "A") } - func testErrorResult() async throws { + @Test func errorResult() async throws { let loaderErrorMessage = "TEST" // Test throwing loader without auto-executing @@ -606,7 +607,7 @@ final class DataLoaderTests: XCTestCase { didFailWithError = error as? DataLoaderError } - XCTAssertNil(didFailWithError) + #expect(didFailWithError == nil) var didFailWithError2: DataLoaderError? @@ -627,7 +628,7 @@ final class DataLoaderTests: XCTestCase { break } - XCTAssertEqual(didFailWithErrorText2, loaderErrorMessage) + #expect(didFailWithErrorText2 == loaderErrorMessage) // Test throwing loader with auto-executing let throwLoaderAutoExecute = DataLoader( @@ -657,6 +658,6 @@ final class DataLoaderTests: XCTestCase { break } - XCTAssertEqual(didFailWithErrorText3, loaderErrorMessage) + #expect(didFailWithErrorText3 == loaderErrorMessage) } } diff --git a/Tests/DataLoaderTests/DataLoaderAbuseTests.swift b/Tests/DataLoaderTests/DataLoaderAbuseTests.swift index 8f679a1..5817f85 100644 --- a/Tests/DataLoaderTests/DataLoaderAbuseTests.swift +++ b/Tests/DataLoaderTests/DataLoaderAbuseTests.swift @@ -1,15 +1,12 @@ import NIOPosix -import XCTest +import Testing @testable import DataLoader /// Provides descriptive error messages for API abuse -class DataLoaderAbuseTests: XCTestCase { - func testFuntionWithNoValues() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } +struct DataLoaderAbuseTests { + @Test func funtionWithNoValues() throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton let identityLoader = DataLoader( options: DataLoaderOptions(batchingEnabled: false) @@ -19,17 +16,14 @@ class DataLoaderAbuseTests: XCTestCase { let value = try identityLoader.load(key: 1, on: eventLoopGroup) - XCTAssertThrowsError( - try value.wait(), - "Did not return value for key: 1" - ) + let error = #expect(throws: DataLoaderError.self) { + try value.wait() + } + #expect(error.testDescription == #".noValueForKey("Did not return value for key: 1")"#) } - func testBatchFuntionMustPromiseAnArrayOfCorrectLength() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + @Test func batchFuntionMustPromiseAnArrayOfCorrectLength() throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton let identityLoader = DataLoader { _ in eventLoopGroup.next().makeSucceededFuture([]) @@ -37,17 +31,17 @@ class DataLoaderAbuseTests: XCTestCase { let value = try identityLoader.load(key: 1, on: eventLoopGroup) - XCTAssertThrowsError( - try value.wait(), - "The function did not return an array of the same length as the array of keys. \nKeys count: 1\nValues count: 0" + let error = #expect(throws: Error.self) { + try value.wait() + } + #expect( + error.testDescription + == #".typeError("The function did not return an array of the same length as the array of keys. \nKeys count: 1\nValues count: 0")"# ) } - func testBatchFuntionWithSomeValues() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + @Test func batchFuntionWithSomeValues() throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton let identityLoader = DataLoader { keys in var results = [DataLoaderFutureValue]() @@ -68,16 +62,15 @@ class DataLoaderAbuseTests: XCTestCase { let value1 = try identityLoader.load(key: 1, on: eventLoopGroup) let value2 = try identityLoader.load(key: 2, on: eventLoopGroup) - XCTAssertThrowsError(try value2.wait()) + #expect(throws: Error.self) { + try value2.wait() + } - XCTAssertTrue(try value1.wait() == 1) + #expect(try value1.wait() == 1) } - func testFuntionWithSomeValues() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + @Test func funtionWithSomeValues() throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton let identityLoader = DataLoader( options: DataLoaderOptions(batchingEnabled: false) @@ -100,8 +93,10 @@ class DataLoaderAbuseTests: XCTestCase { let value1 = try identityLoader.load(key: 1, on: eventLoopGroup) let value2 = try identityLoader.load(key: 2, on: eventLoopGroup) - XCTAssertThrowsError(try value2.wait()) + #expect(throws: Error.self) { + try value2.wait() + } - XCTAssertTrue(try value1.wait() == 1) + #expect(try value1.wait() == 1) } } diff --git a/Tests/DataLoaderTests/DataLoaderAsyncTests.swift b/Tests/DataLoaderTests/DataLoaderAsyncTests.swift index a3c66e4..b6fa870 100644 --- a/Tests/DataLoaderTests/DataLoaderAsyncTests.swift +++ b/Tests/DataLoaderTests/DataLoaderAsyncTests.swift @@ -1,116 +1,101 @@ import NIOPosix -import XCTest +import Testing @testable import DataLoader -#if compiler(>=5.5) && canImport(_Concurrency) +actor Concurrent { + var wrappedValue: T - @available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) - actor Concurrent { - var wrappedValue: T - - func nonmutating(_ action: (T) throws -> Returned) async rethrows -> Returned { - try action(wrappedValue) - } - - func mutating(_ action: (inout T) throws -> Returned) async rethrows -> Returned { - try action(&wrappedValue) - } - - init(_ value: T) { - wrappedValue = value - } + func nonmutating(_ action: (T) throws -> Returned) async rethrows -> Returned { + try action(wrappedValue) } - /// Primary API - @available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) - final class DataLoaderAsyncTests: XCTestCase { - /// Builds a really really simple data loader with async await - func testReallyReallySimpleDataLoader() async throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + func mutating(_ action: (inout T) throws -> Returned) async rethrows -> Returned { + try action(&wrappedValue) + } - let identityLoader = DataLoader( - on: eventLoopGroup.next(), - options: DataLoaderOptions(batchingEnabled: false) - ) { keys async in - let task = Task { - keys.map { DataLoaderFutureValue.success($0) } - } - return await task.value + init(_ value: T) { + wrappedValue = value + } +} + +/// Primary API +struct DataLoaderAsyncTests { + /// Builds a really really simple data loader with async await + @Test func reallyReallySimpleDataLoader() async throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton + + let identityLoader = DataLoader( + on: eventLoopGroup.next(), + options: DataLoaderOptions(batchingEnabled: false) + ) { keys async in + let task = Task { + keys.map { DataLoaderFutureValue.success($0) } } + return await task.value + } - let value = try await identityLoader.load(key: 1, on: eventLoopGroup) + let value = try await identityLoader.load(key: 1, on: eventLoopGroup) - XCTAssertEqual(value, 1) - } + #expect(value == 1) + } - /// Supports loading multiple keys in one call - func testLoadingMultipleKeys() async throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + /// Supports loading multiple keys in one call + @Test func loadingMultipleKeys() async throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton - let identityLoader = DataLoader(on: eventLoopGroup.next()) { keys in - let task = Task { - keys.map { DataLoaderFutureValue.success($0) } - } - return await task.value + let identityLoader = DataLoader(on: eventLoopGroup.next()) { keys in + let task = Task { + keys.map { DataLoaderFutureValue.success($0) } } + return await task.value + } - let values = try await identityLoader.loadMany(keys: [1, 2], on: eventLoopGroup) - - XCTAssertEqual(values, [1, 2]) + let values = try await identityLoader.loadMany(keys: [1, 2], on: eventLoopGroup) - let empty = try await identityLoader.loadMany(keys: [], on: eventLoopGroup) + #expect(values == [1, 2]) - XCTAssertTrue(empty.isEmpty) - } + let empty = try await identityLoader.loadMany(keys: [], on: eventLoopGroup) - /// Batches multiple requests - func testMultipleRequests() async throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + #expect(empty.isEmpty) + } - let loadCalls = Concurrent<[[Int]]>([]) - - let identityLoader = DataLoader( - on: eventLoopGroup.next(), - options: DataLoaderOptions( - batchingEnabled: true, - executionPeriod: nil - ) - ) { keys in - await loadCalls.mutating { $0.append(keys) } - let task = Task { - keys.map { DataLoaderFutureValue.success($0) } - } - return await task.value + /// Batches multiple requests + @Test func multipleRequests() async throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton + + let loadCalls = Concurrent<[[Int]]>([]) + + let identityLoader = DataLoader( + on: eventLoopGroup.next(), + options: DataLoaderOptions( + batchingEnabled: true, + executionPeriod: nil + ) + ) { keys in + await loadCalls.mutating { $0.append(keys) } + let task = Task { + keys.map { DataLoaderFutureValue.success($0) } } + return await task.value + } - async let value1 = identityLoader.load(key: 1, on: eventLoopGroup) - async let value2 = identityLoader.load(key: 2, on: eventLoopGroup) + async let value1 = identityLoader.load(key: 1, on: eventLoopGroup) + async let value2 = identityLoader.load(key: 2, on: eventLoopGroup) - // Have to wait for a split second because Tasks may not be executed before this - // statement - try await Task.sleep(nanoseconds: 500_000_000) + // Have to wait for a split second because Tasks may not be executed before this + // statement + try await Task.sleep(nanoseconds: 500_000_000) - XCTAssertNoThrow(try identityLoader.execute()) + try identityLoader.execute() - let result1 = try await value1 - XCTAssertEqual(result1, 1) - let result2 = try await value2 - XCTAssertEqual(result2, 2) + let result1 = try await value1 + #expect(result1 == 1) + let result2 = try await value2 + #expect(result2 == 2) - let calls = await loadCalls.wrappedValue - XCTAssertEqual(calls.count, 1) - XCTAssertEqual(calls.map { $0.sorted() }, [[1, 2]]) - } + let calls = await loadCalls.wrappedValue + #expect(calls.count == 1) + #expect(calls.map { $0.sorted() } == [[1, 2]]) } - -#endif +} diff --git a/Tests/DataLoaderTests/DataLoaderTests.swift b/Tests/DataLoaderTests/DataLoaderTests.swift index 8c8765f..140d570 100644 --- a/Tests/DataLoaderTests/DataLoaderTests.swift +++ b/Tests/DataLoaderTests/DataLoaderTests.swift @@ -1,17 +1,15 @@ +import Dispatch import NIOCore import NIOPosix -import XCTest +import Testing @testable import DataLoader /// Primary API -final class DataLoaderTests: XCTestCase { +struct DataLoaderTests { /// Builds a really really simple data loader' - func testReallyReallySimpleDataLoader() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + @Test func reallyReallySimpleDataLoader() throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton let identityLoader = DataLoader( options: DataLoaderOptions(batchingEnabled: false) @@ -23,15 +21,12 @@ final class DataLoaderTests: XCTestCase { let value = try identityLoader.load(key: 1, on: eventLoopGroup).wait() - XCTAssertEqual(value, 1) + #expect(value == 1) } /// Supports loading multiple keys in one call - func testLoadingMultipleKeys() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + @Test func loadingMultipleKeys() throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton let identityLoader = DataLoader { keys in let results = keys.map { DataLoaderFutureValue.success($0) } @@ -41,19 +36,16 @@ final class DataLoaderTests: XCTestCase { let values = try identityLoader.loadMany(keys: [1, 2], on: eventLoopGroup).wait() - XCTAssertEqual(values, [1, 2]) + #expect(values == [1, 2]) let empty = try identityLoader.loadMany(keys: [], on: eventLoopGroup).wait() - XCTAssertTrue(empty.isEmpty) + #expect(empty.isEmpty) } /// Batches multiple requests - func testMultipleRequests() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + @Test func multipleRequests() throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton var loadCalls = [[Int]]() @@ -72,20 +64,17 @@ final class DataLoaderTests: XCTestCase { let value1 = try identityLoader.load(key: 1, on: eventLoopGroup) let value2 = try identityLoader.load(key: 2, on: eventLoopGroup) - XCTAssertNoThrow(try identityLoader.execute()) + try identityLoader.execute() - XCTAssertEqual(try value1.wait(), 1) - XCTAssertEqual(try value2.wait(), 2) + #expect(try value1.wait() == 1) + #expect(try value2.wait() == 2) - XCTAssertEqual(loadCalls, [[1, 2]]) + #expect(loadCalls == [[1, 2]]) } /// Batches multiple requests with max batch sizes - func testMultipleRequestsWithMaxBatchSize() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + @Test func multipleRequestsWithMaxBatchSize() throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton var loadCalls = [[Int]]() @@ -106,21 +95,18 @@ final class DataLoaderTests: XCTestCase { let value2 = try identityLoader.load(key: 2, on: eventLoopGroup) let value3 = try identityLoader.load(key: 3, on: eventLoopGroup) - XCTAssertNoThrow(try identityLoader.execute()) + try identityLoader.execute() - XCTAssertEqual(try value1.wait(), 1) - XCTAssertEqual(try value2.wait(), 2) - XCTAssertEqual(try value3.wait(), 3) + #expect(try value1.wait() == 1) + #expect(try value2.wait() == 2) + #expect(try value3.wait() == 3) - XCTAssertEqual(loadCalls, [[1, 2], [3]]) + #expect(loadCalls == [[1, 2], [3]]) } /// Coalesces identical requests - func testCoalescesIdenticalRequests() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + @Test func coalescesIdenticalRequests() throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton var loadCalls = [[Int]]() @@ -136,20 +122,17 @@ final class DataLoaderTests: XCTestCase { let value1 = try identityLoader.load(key: 1, on: eventLoopGroup) let value2 = try identityLoader.load(key: 1, on: eventLoopGroup) - XCTAssertNoThrow(try identityLoader.execute()) + try identityLoader.execute() - XCTAssertTrue(try value1.map { $0 }.wait() == 1) - XCTAssertTrue(try value2.map { $0 }.wait() == 1) + #expect(try value1.map { $0 }.wait() == 1) + #expect(try value2.map { $0 }.wait() == 1) - XCTAssertTrue(loadCalls == [[1]]) + #expect(loadCalls == [[1]]) } /// Caches repeated requests - func testCachesRepeatedRequests() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + @Test func cachesRepeatedRequests() throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton var loadCalls = [[String]]() @@ -165,39 +148,36 @@ final class DataLoaderTests: XCTestCase { let value1 = try identityLoader.load(key: "A", on: eventLoopGroup) let value2 = try identityLoader.load(key: "B", on: eventLoopGroup) - XCTAssertNoThrow(try identityLoader.execute()) + try identityLoader.execute() - XCTAssertTrue(try value1.wait() == "A") - XCTAssertTrue(try value2.wait() == "B") - XCTAssertTrue(loadCalls == [["A", "B"]]) + #expect(try value1.wait() == "A") + #expect(try value2.wait() == "B") + #expect(loadCalls == [["A", "B"]]) let value3 = try identityLoader.load(key: "A", on: eventLoopGroup) let value4 = try identityLoader.load(key: "C", on: eventLoopGroup) - XCTAssertNoThrow(try identityLoader.execute()) + try identityLoader.execute() - XCTAssertTrue(try value3.wait() == "A") - XCTAssertTrue(try value4.wait() == "C") - XCTAssertTrue(loadCalls == [["A", "B"], ["C"]]) + #expect(try value3.wait() == "A") + #expect(try value4.wait() == "C") + #expect(loadCalls == [["A", "B"], ["C"]]) let value5 = try identityLoader.load(key: "A", on: eventLoopGroup) let value6 = try identityLoader.load(key: "B", on: eventLoopGroup) let value7 = try identityLoader.load(key: "C", on: eventLoopGroup) - XCTAssertNoThrow(try identityLoader.execute()) + try identityLoader.execute() - XCTAssertTrue(try value5.wait() == "A") - XCTAssertTrue(try value6.wait() == "B") - XCTAssertTrue(try value7.wait() == "C") - XCTAssertTrue(loadCalls == [["A", "B"], ["C"]]) + #expect(try value5.wait() == "A") + #expect(try value6.wait() == "B") + #expect(try value7.wait() == "C") + #expect(loadCalls == [["A", "B"], ["C"]]) } /// Clears single value in loader - func testClearSingleValueLoader() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + @Test func clearSingleValueLoader() throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton var loadCalls = [[String]]() @@ -213,30 +193,27 @@ final class DataLoaderTests: XCTestCase { let value1 = try identityLoader.load(key: "A", on: eventLoopGroup) let value2 = try identityLoader.load(key: "B", on: eventLoopGroup) - XCTAssertNoThrow(try identityLoader.execute()) + try identityLoader.execute() - XCTAssertTrue(try value1.wait() == "A") - XCTAssertTrue(try value2.wait() == "B") - XCTAssertTrue(loadCalls == [["A", "B"]]) + #expect(try value1.wait() == "A") + #expect(try value2.wait() == "B") + #expect(loadCalls == [["A", "B"]]) _ = identityLoader.clear(key: "A") let value3 = try identityLoader.load(key: "A", on: eventLoopGroup) let value4 = try identityLoader.load(key: "B", on: eventLoopGroup) - XCTAssertNoThrow(try identityLoader.execute()) + try identityLoader.execute() - XCTAssertTrue(try value3.wait() == "A") - XCTAssertTrue(try value4.wait() == "B") - XCTAssertTrue(loadCalls == [["A", "B"], ["A"]]) + #expect(try value3.wait() == "A") + #expect(try value4.wait() == "B") + #expect(loadCalls == [["A", "B"], ["A"]]) } /// Clears all values in loader - func testClearsAllValuesInLoader() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + @Test func clearsAllValuesInLoader() throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton var loadCalls = [[String]]() @@ -252,30 +229,27 @@ final class DataLoaderTests: XCTestCase { let value1 = try identityLoader.load(key: "A", on: eventLoopGroup) let value2 = try identityLoader.load(key: "B", on: eventLoopGroup) - XCTAssertNoThrow(try identityLoader.execute()) + try identityLoader.execute() - XCTAssertTrue(try value1.wait() == "A") - XCTAssertTrue(try value2.wait() == "B") - XCTAssertTrue(loadCalls == [["A", "B"]]) + #expect(try value1.wait() == "A") + #expect(try value2.wait() == "B") + #expect(loadCalls == [["A", "B"]]) _ = identityLoader.clearAll() let value3 = try identityLoader.load(key: "A", on: eventLoopGroup) let value4 = try identityLoader.load(key: "B", on: eventLoopGroup) - XCTAssertNoThrow(try identityLoader.execute()) + try identityLoader.execute() - XCTAssertTrue(try value3.wait() == "A") - XCTAssertTrue(try value4.wait() == "B") - XCTAssertTrue(loadCalls == [["A", "B"], ["A", "B"]]) + #expect(try value3.wait() == "A") + #expect(try value4.wait() == "B") + #expect(loadCalls == [["A", "B"], ["A", "B"]]) } /// Allows priming the cache - func testAllowsPrimingTheCache() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + @Test func allowsPrimingTheCache() throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton var loadCalls = [[String]]() @@ -293,19 +267,16 @@ final class DataLoaderTests: XCTestCase { let value1 = try identityLoader.load(key: "A", on: eventLoopGroup) let value2 = try identityLoader.load(key: "B", on: eventLoopGroup) - XCTAssertNoThrow(try identityLoader.execute()) + try identityLoader.execute() - XCTAssertTrue(try value1.wait() == "A") - XCTAssertTrue(try value2.wait() == "B") - XCTAssertTrue(loadCalls == [["B"]]) + #expect(try value1.wait() == "A") + #expect(try value2.wait() == "B") + #expect(loadCalls == [["B"]]) } /// Does not prime keys that already exist - func testDoesNotPrimeKeysThatAlreadyExist() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + @Test func doesNotPrimeKeysThatAlreadyExist() throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton var loadCalls = [[String]]() @@ -323,10 +294,10 @@ final class DataLoaderTests: XCTestCase { let value1 = try identityLoader.load(key: "A", on: eventLoopGroup) let value2 = try identityLoader.load(key: "B", on: eventLoopGroup) - XCTAssertNoThrow(try identityLoader.execute()) + try identityLoader.execute() - XCTAssertTrue(try value1.wait() == "X") - XCTAssertTrue(try value2.wait() == "B") + #expect(try value1.wait() == "X") + #expect(try value2.wait() == "B") _ = identityLoader.prime(key: "A", value: "Y", on: eventLoopGroup) _ = identityLoader.prime(key: "B", value: "Y", on: eventLoopGroup) @@ -334,20 +305,17 @@ final class DataLoaderTests: XCTestCase { let value3 = try identityLoader.load(key: "A", on: eventLoopGroup) let value4 = try identityLoader.load(key: "B", on: eventLoopGroup) - XCTAssertNoThrow(try identityLoader.execute()) + try identityLoader.execute() - XCTAssertTrue(try value3.wait() == "X") - XCTAssertTrue(try value4.wait() == "B") + #expect(try value3.wait() == "X") + #expect(try value4.wait() == "B") - XCTAssertTrue(loadCalls == [["B"]]) + #expect(loadCalls == [["B"]]) } /// Allows forcefully priming the cache - func testAllowsForcefullyPrimingTheCache() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + @Test func allowsForcefullyPrimingTheCache() throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton var loadCalls = [[String]]() @@ -365,10 +333,10 @@ final class DataLoaderTests: XCTestCase { let value1 = try identityLoader.load(key: "A", on: eventLoopGroup) let value2 = try identityLoader.load(key: "B", on: eventLoopGroup) - XCTAssertNoThrow(try identityLoader.execute()) + try identityLoader.execute() - XCTAssertTrue(try value1.wait() == "X") - XCTAssertTrue(try value2.wait() == "B") + #expect(try value1.wait() == "X") + #expect(try value2.wait() == "B") _ = identityLoader.clear(key: "A").prime(key: "A", value: "Y", on: eventLoopGroup) _ = identityLoader.clear(key: "B").prime(key: "B", value: "Y", on: eventLoopGroup) @@ -376,22 +344,21 @@ final class DataLoaderTests: XCTestCase { let value3 = try identityLoader.load(key: "A", on: eventLoopGroup) let value4 = try identityLoader.load(key: "B", on: eventLoopGroup) - XCTAssertNoThrow(try identityLoader.execute()) + try identityLoader.execute() - XCTAssertTrue(try value3.wait() == "Y") - XCTAssertTrue(try value4.wait() == "Y") + #expect(try value3.wait() == "Y") + #expect(try value4.wait() == "Y") - XCTAssertTrue(loadCalls == [["B"]]) + #expect(loadCalls == [["B"]]) } /// Caches repeated requests, even if initiated asyncronously - func testCacheConcurrency() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + @Test func cacheConcurrency() throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton - let identityLoader = DataLoader(options: DataLoaderOptions()) { keys in + let identityLoader = DataLoader( + options: DataLoaderOptions(executionPeriod: nil) + ) { keys in let results = keys.map { DataLoaderFutureValue.success($0) } return eventLoopGroup.next().makeSucceededFuture(results) @@ -410,17 +377,14 @@ final class DataLoaderTests: XCTestCase { // Sleep for a few ms ensure that value1 & value2 are populated before continuing usleep(1000) - XCTAssertNoThrow(try identityLoader.execute()) + try identityLoader.execute() // Test that the futures themselves are equal (not just the value). - XCTAssertEqual(value1, value2) + #expect(value1 == value2) } - func testAutoExecute() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + @Test func autoExecute() throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton let identityLoader = DataLoader( options: DataLoaderOptions(executionPeriod: .milliseconds(2)) @@ -430,22 +394,18 @@ final class DataLoaderTests: XCTestCase { return eventLoopGroup.next().makeSucceededFuture(results) } - var value: String? + let promise = eventLoopGroup.next().makePromise(of: String.self) _ = try identityLoader.load(key: "A", on: eventLoopGroup).map { result in - value = result + promise.succeed(result) } - // Don't manually call execute, but wait for more than 2ms - usleep(3000) - - XCTAssertNotNil(value) + // Don't manually call execute, but wait for the result + let value = try promise.futureResult.wait() + #expect(value == "A") } - func testErrorResult() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + @Test func errorResult() throws { + let eventLoopGroup = MultiThreadedEventLoopGroup.singleton let loaderErrorMessage = "TEST" @@ -457,11 +417,10 @@ final class DataLoaderTests: XCTestCase { } let value = try throwLoader.load(key: 1, on: eventLoopGroup) - XCTAssertNoThrow(try throwLoader.execute()) - XCTAssertThrowsError( - try value.wait(), - loaderErrorMessage - ) + try throwLoader.execute() + #expect(throws: DataLoaderError.self) { + try value.wait() + } // Test throwing loader with auto-executing let throwLoaderAutoExecute = DataLoader( @@ -470,9 +429,8 @@ final class DataLoaderTests: XCTestCase { throw DataLoaderError.typeError(loaderErrorMessage) } - XCTAssertThrowsError( - try throwLoaderAutoExecute.load(key: 1, on: eventLoopGroup).wait(), - loaderErrorMessage - ) + #expect(throws: DataLoaderError.self) { + try throwLoaderAutoExecute.load(key: 1, on: eventLoopGroup).wait() + } } }