From 430704797a63b144fb9ebca268dee9f987d706c5 Mon Sep 17 00:00:00 2001 From: Jon Shier Date: Sat, 3 Apr 2021 22:30:36 -0400 Subject: [PATCH] Refactor Tests Using Firewalk (#440) * Refactor tests with Endpoint. * Increase stress tests. * Update actions to use Firewalk. * Test AFI on Catalyst. * Fix flaky tests. * Refactor test. * Fix test reliability on tvOS. --- .github/workflows/ci.yml | 48 +- .ruby-version | 2 +- AlamofireImage.xcodeproj/project.pbxproj | 16 + .../xcschemes/AlamofireImage macOS.xcscheme | 5 - Cartfile.resolved | 2 +- Gemfile.lock | 65 +-- Package.resolved | 4 +- Source/ImageDownloader.swift | 2 +- Tests/CGSize.ScreenScaling.swift | 39 ++ Tests/ImageCacheTests.swift | 10 +- Tests/ImageDownloaderStressTests.swift | 64 ++- Tests/ImageDownloaderTests.swift | 216 ++++---- Tests/ImageFilterTests.swift | 2 +- Tests/RequestTests.swift | 253 +++------- Tests/TestHelpers.swift | 469 ++++++++++++++++++ Tests/UIButtonTests.swift | 270 +++++----- Tests/UIImage+AlamofireImageTests.swift | 6 + Tests/UIImageTests.swift | 4 +- Tests/UIImageViewTests.swift | 150 +++--- carthage.sh | 19 - 20 files changed, 987 insertions(+), 659 deletions(-) create mode 100644 Tests/CGSize.ScreenScaling.swift create mode 100644 Tests/TestHelpers.swift delete mode 100755 carthage.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ac94e148..60badac3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: - name: Checkout uses: actions/checkout@v2 - name: Dependencies - run: ./carthage.sh bootstrap --no-use-binaries --platform macOS + run: carthage bootstrap --no-build - name: macOS (5.1) run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -workspace "AlamofireImage.xcworkspace" -scheme "AlamofireImage macOS" -destination "platform=macOS" clean build | xcpretty macOS_5_2: @@ -30,8 +30,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 + - name: Install Firewalk + run: brew install alamofire/alamofire/firewalk && firewalk & - name: Dependencies - run: ./carthage.sh bootstrap --no-use-binaries --platform macOS + run: carthage bootstrap --no-build - name: macOS (5.2) run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -workspace "AlamofireImage.xcworkspace" -scheme "AlamofireImage macOS" -destination "platform=macOS" clean test | xcpretty macOS_5_3: @@ -42,21 +44,26 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 + - name: Install Firewalk + run: brew install alamofire/alamofire/firewalk && firewalk & - name: Dependencies - run: ./carthage.sh bootstrap --no-use-binaries --platform macOS + run: carthage bootstrap --no-build - name: macOS (5.3) run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -workspace "AlamofireImage.xcworkspace" -scheme "AlamofireImage macOS" -destination "platform=macOS" clean test | xcpretty - # Catalyst: - # name: Test Catalyst - # runs-on: macOS-latest - # env: - # DEVELOPER_DIR: /Applications/Xcode_11.6.app/Contents/Developer - # steps: - # - uses: actions/checkout@v2 - # - name: Dependencies - # run: carthage bootstrap --no-use-binaries --platform macCatalyst - # - name: Catalyst - # run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -workspace "AlamofireImage.xcworkspace" -scheme "Alamofire iOS" -destination "platform=macOS" clean test | xcpretty + Catalyst: + name: Test Catalyst + runs-on: macOS-latest + env: + DEVELOPER_DIR: /Applications/Xcode_12.4.app/Contents/Developer + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Install Firewalk + run: brew install alamofire/alamofire/firewalk && firewalk & + - name: Dependencies + run: carthage bootstrap --no-build + - name: Catalyst + run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -workspace "AlamofireImage.xcworkspace" -scheme "AlamofireImage iOS" -destination "platform=macOS" clean test | xcpretty iOS: name: Test iOS runs-on: macOS-latest @@ -68,8 +75,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 + - name: Install Firewalk + run: brew install alamofire/alamofire/firewalk && firewalk & - name: Dependencies - run: ./carthage.sh bootstrap --no-use-binaries --platform iOS + run: carthage bootstrap --no-build - name: iOS - ${{ matrix.destination }} run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -workspace "AlamofireImage.xcworkspace" -scheme "AlamofireImage iOS" -destination "${{ matrix.destination }}" clean test | xcpretty tvOS: @@ -83,8 +92,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 + - name: Install Firewalk + run: brew install alamofire/alamofire/firewalk && firewalk & - name: Dependencies - run: ./carthage.sh bootstrap --no-use-binaries --platform tvOS + run: carthage bootstrap --no-build - name: tvOS - ${{ matrix.destination }} run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -workspace "AlamofireImage.xcworkspace" -scheme "AlamofireImage tvOS" -destination "${{ matrix.destination }}" clean test | xcpretty watchOS: @@ -99,7 +110,7 @@ jobs: - name: Checkout uses: actions/checkout@v2 - name: Dependencies - run: ./carthage.sh bootstrap --no-use-binaries --platform watchOS + run: carthage bootstrap --no-build - name: watchOS - ${{ matrix.destination }} run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -workspace "AlamofireImage.xcworkspace" -scheme "AlamofireImage watchOS" -destination "${{ matrix.destination }}" clean build | xcpretty spm: @@ -108,7 +119,8 @@ jobs: env: DEVELOPER_DIR: /Applications/Xcode_12.4.app/Contents/Developer steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v2 - name: SPM Build run: swift build -c debug diff --git a/.ruby-version b/.ruby-version index 860487ca..37c2961c 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.7.1 +2.7.2 diff --git a/AlamofireImage.xcodeproj/project.pbxproj b/AlamofireImage.xcodeproj/project.pbxproj index 733720e9..2dc4cccc 100644 --- a/AlamofireImage.xcodeproj/project.pbxproj +++ b/AlamofireImage.xcodeproj/project.pbxproj @@ -7,6 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + 31FA4D672619207200CEAD2B /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FA4D662619207200CEAD2B /* TestHelpers.swift */; }; + 31FA4D682619207200CEAD2B /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FA4D662619207200CEAD2B /* TestHelpers.swift */; }; + 31FA4D692619207200CEAD2B /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FA4D662619207200CEAD2B /* TestHelpers.swift */; }; + 31FA4D7926192E4500CEAD2B /* CGSize.ScreenScaling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FA4D7826192E4500CEAD2B /* CGSize.ScreenScaling.swift */; }; + 31FA4D7A26192E4500CEAD2B /* CGSize.ScreenScaling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FA4D7826192E4500CEAD2B /* CGSize.ScreenScaling.swift */; }; + 31FA4D7B26192E4500CEAD2B /* CGSize.ScreenScaling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FA4D7826192E4500CEAD2B /* CGSize.ScreenScaling.swift */; }; 4C0893EC1B936A7A005125D9 /* UIImage+AlamofireImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0893EB1B936A7A005125D9 /* UIImage+AlamofireImageTests.swift */; }; 4C0893F51B937404005125D9 /* UIImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0893F41B937404005125D9 /* UIImageTests.swift */; }; 4C0894E41B9382EB005125D9 /* apple.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 4C0894501B9382EB005125D9 /* apple.jpg */; }; @@ -566,6 +572,8 @@ 3143544B1F4A16FE00266E5F /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 3143544C1F4A16FE00266E5F /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; 314354501F4A16FE00266E5F /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; }; + 31FA4D662619207200CEAD2B /* TestHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestHelpers.swift; sourceTree = ""; }; + 31FA4D7826192E4500CEAD2B /* CGSize.ScreenScaling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGSize.ScreenScaling.swift; sourceTree = ""; }; 4C0893EB1B936A7A005125D9 /* UIImage+AlamofireImageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+AlamofireImageTests.swift"; sourceTree = ""; }; 4C0893F41B937404005125D9 /* UIImageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageTests.swift; sourceTree = ""; }; 4C0894501B9382EB005125D9 /* apple.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = apple.jpg; sourceTree = ""; }; @@ -1239,6 +1247,7 @@ 4CBE9C642401FE2100B2AAC0 /* Helpers */ = { isa = PBXGroup; children = ( + 31FA4D662619207200CEAD2B /* TestHelpers.swift */, B30AFF7B23F49CC500883238 /* ThrowingURLRequestConvertible.swift */, ); name = Helpers; @@ -1265,6 +1274,7 @@ children = ( 4CD5BCF41D7FBE380055E232 /* AFError+AlamofireImageTests.swift */, 4C7DD7EE224EC07000249836 /* AFResult+AlamofireImageTests.swift */, + 31FA4D7826192E4500CEAD2B /* CGSize.ScreenScaling.swift */, 4C0893EB1B936A7A005125D9 /* UIImage+AlamofireImageTests.swift */, ); name = Extensions; @@ -2057,8 +2067,10 @@ 4C16B3AA1BA93ADB00A66EF0 /* ImageFilterTests.swift in Sources */, 4C16B3A71BA93ADB00A66EF0 /* BaseTestCase.swift in Sources */, B30AFF7F23F4A3FD00883238 /* ThrowingURLRequestConvertible.swift in Sources */, + 31FA4D692619207200CEAD2B /* TestHelpers.swift in Sources */, 4CBE8FB92316008300782A2E /* ImageDownloaderStressTests.swift in Sources */, 4C16B3AD1BA93ADB00A66EF0 /* UIImageViewTests.swift in Sources */, + 31FA4D7B26192E4500CEAD2B /* CGSize.ScreenScaling.swift in Sources */, 4C16B3A91BA93ADB00A66EF0 /* ImageDownloaderTests.swift in Sources */, 4C16B3AC1BA93ADB00A66EF0 /* UIImageTests.swift in Sources */, ); @@ -2109,8 +2121,10 @@ 4CEBB5401B93C622001391DE /* ImageCacheTests.swift in Sources */, 4C0893EC1B936A7A005125D9 /* UIImage+AlamofireImageTests.swift in Sources */, B30AFF7D23F4A3FB00883238 /* ThrowingURLRequestConvertible.swift in Sources */, + 31FA4D672619207200CEAD2B /* TestHelpers.swift in Sources */, 4CBE8FB72316008300782A2E /* ImageDownloaderStressTests.swift in Sources */, 4C1624851AABE8E600A0385D /* BaseTestCase.swift in Sources */, + 31FA4D7926192E4500CEAD2B /* CGSize.ScreenScaling.swift in Sources */, 4C0893F51B937404005125D9 /* UIImageTests.swift in Sources */, 4C5874861B93F81800407E58 /* ImageDownloaderTests.swift in Sources */, ); @@ -2145,8 +2159,10 @@ 4CEBB5411B93C622001391DE /* ImageCacheTests.swift in Sources */, 4C86C1121DA0B3400032ECC3 /* UIImageViewTests.swift in Sources */, B30AFF7E23F4A3FC00883238 /* ThrowingURLRequestConvertible.swift in Sources */, + 31FA4D682619207200CEAD2B /* TestHelpers.swift in Sources */, 4CBE8FB82316008300782A2E /* ImageDownloaderStressTests.swift in Sources */, 4C5874871B93F81800407E58 /* ImageDownloaderTests.swift in Sources */, + 31FA4D7A26192E4500CEAD2B /* CGSize.ScreenScaling.swift in Sources */, 4C86C1131DA0B3440032ECC3 /* UIImage+AlamofireImageTests.swift in Sources */, 4C0897EC1B93BC0D005125D9 /* RequestTests.swift in Sources */, ); diff --git a/AlamofireImage.xcodeproj/xcshareddata/xcschemes/AlamofireImage macOS.xcscheme b/AlamofireImage.xcodeproj/xcshareddata/xcschemes/AlamofireImage macOS.xcscheme index e9a60e18..2a4c86c5 100644 --- a/AlamofireImage.xcodeproj/xcshareddata/xcschemes/AlamofireImage macOS.xcscheme +++ b/AlamofireImage.xcodeproj/xcshareddata/xcschemes/AlamofireImage macOS.xcscheme @@ -59,11 +59,6 @@ value = "1" isEnabled = "YES"> - - 0.7) + CFPropertyList (3.0.3) + activesupport (5.2.5) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - algoliasearch (1.27.1) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) atomos (0.1.3) claide (1.0.3) - cocoapods (1.9.1) - activesupport (>= 4.0.2, < 5) + cocoapods (1.10.1) + addressable (~> 2.6) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.9.1) + cocoapods-core (= 1.10.1) cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.2.2, < 2.0) + cocoapods-downloader (>= 1.4.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-stats (>= 1.0.0, < 2.0) cocoapods-trunk (>= 1.4.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) @@ -30,38 +31,39 @@ GEM molinillo (~> 0.6.6) nap (~> 1.0) ruby-macho (~> 1.4) - xcodeproj (>= 1.14.0, < 2.0) - cocoapods-core (1.9.1) - activesupport (>= 4.0.2, < 6) + xcodeproj (>= 1.19.0, < 2.0) + cocoapods-core (1.10.1) + activesupport (> 5.0, < 6) + addressable (~> 2.6) algoliasearch (~> 1.0) concurrent-ruby (~> 1.1) fuzzy_match (~> 2.0.4) nap (~> 1.0) netrc (~> 0.11) + public_suffix typhoeus (~> 1.0) cocoapods-deintegrate (1.0.4) - cocoapods-downloader (1.3.0) + cocoapods-downloader (1.4.0) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) - cocoapods-stats (1.1.0) - cocoapods-trunk (1.4.1) + cocoapods-trunk (1.5.0) nap (>= 0.8, < 2.0) netrc (~> 0.11) - cocoapods-try (1.1.0) + cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.1.6) + concurrent-ruby (1.1.8) escape (0.0.4) ethon (0.12.0) ffi (>= 1.3.0) - ffi (1.12.2) + ffi (1.15.0) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) httpclient (2.8.3) - i18n (0.9.5) + i18n (1.8.10) concurrent-ruby (~> 1.0) - jazzy (0.13.2) + jazzy (0.13.6) cocoapods (~> 1.5) mustache (~> 1.1) open4 @@ -70,34 +72,35 @@ GEM sassc (~> 2.1) sqlite3 (~> 1.3) xcinvoke (~> 0.3.0) - json (2.3.0) + json (2.5.1) liferaft (0.0.6) - minitest (5.14.0) + minitest (5.14.4) molinillo (0.6.6) mustache (1.1.1) - nanaimo (0.2.6) + nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) open4 (1.3.4) - redcarpet (3.5.0) - rouge (3.17.0) + public_suffix (4.0.6) + redcarpet (3.5.1) + rouge (3.26.0) ruby-macho (1.4.0) - sassc (2.2.1) + sassc (2.4.0) ffi (~> 1.9) sqlite3 (1.4.2) thread_safe (0.3.6) - typhoeus (1.3.1) + typhoeus (1.4.0) ethon (>= 0.9.0) - tzinfo (1.2.7) + tzinfo (1.2.9) thread_safe (~> 0.1) xcinvoke (0.3.0) liferaft (~> 0.0.6) - xcodeproj (1.15.0) + xcodeproj (1.19.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.6) + nanaimo (~> 0.3.0) PLATFORMS ruby diff --git a/Package.resolved b/Package.resolved index 7378f585..bda1aa16 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/Alamofire/Alamofire.git", "state": { "branch": null, - "revision": "fca036f7aeca07124067cb6e0c12b0ad6359e3d4", - "version": "5.1.0" + "revision": "4d19ad82f80cc71ff829b941ded114c56f4f604c", + "version": "5.4.2" } } ] diff --git a/Source/ImageDownloader.swift b/Source/ImageDownloader.swift index ab878804..119a6445 100644 --- a/Source/ImageDownloader.swift +++ b/Source/ImageDownloader.swift @@ -79,7 +79,7 @@ open class ImageDownloader { case fifo, lifo } - class ResponseHandler { + final class ResponseHandler { let urlID: String let handlerID: String let request: DataRequest diff --git a/Tests/CGSize.ScreenScaling.swift b/Tests/CGSize.ScreenScaling.swift new file mode 100644 index 00000000..2ca16836 --- /dev/null +++ b/Tests/CGSize.ScreenScaling.swift @@ -0,0 +1,39 @@ +// +// CGSize.ScreenScaling.swift +// +// Copyright (c) 2021 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#if canImport(UIKit) + +import UIKit + +extension CGSize { + var scaledToScreen: CGSize { + scaled(by: UIScreen.main.scale) + } + + func scaled(by scale: CGFloat) -> CGSize { + CGSize(width: width / scale, height: height / scale) + } +} + +#endif diff --git a/Tests/ImageCacheTests.swift b/Tests/ImageCacheTests.swift index c3e871bb..05307256 100644 --- a/Tests/ImageCacheTests.swift +++ b/Tests/ImageCacheTests.swift @@ -76,7 +76,7 @@ final class ImageCacheTestCase: BaseTestCase { func testThatItCanAddImageToCacheWithRequestIdentifier() { // Given let image = self.image(forResource: "unicorn", withExtension: "png") - let request = try! URLRequest(url: "https://images.example.com/animals", method: .get) + let request = Endpoint.get.urlRequest let identifier = "-unicorn" // When @@ -114,7 +114,7 @@ final class ImageCacheTestCase: BaseTestCase { // Given let unicornImage = image(forResource: "unicorn", withExtension: "png") let pirateImage = image(forResource: "pirate", withExtension: "jpg") - let request = try! URLRequest(url: "https://images.example.com/animals", method: .get) + let request = Endpoint.get.urlRequest let identifier = "animal" // When @@ -156,7 +156,7 @@ final class ImageCacheTestCase: BaseTestCase { func testThatItCanRemoveImageFromCacheWithRequestIdentifier() { // Given let image = self.image(forResource: "unicorn", withExtension: "png") - let request = try! URLRequest(url: "https://images.example.com/animals", method: .get) + let request = Endpoint.get.urlRequest let identifier = "unicorn" // When @@ -175,7 +175,7 @@ final class ImageCacheTestCase: BaseTestCase { func testThatItCanRemoveImagesFromCacheMatchingRequestIdentifier() { // Given let image = self.image(forResource: "unicorn", withExtension: "png") - let request = try! URLRequest(url: "https://images.example.com/animals", method: .get) + let request = Endpoint.get.urlRequest let identifier1 = "unicorn-100" let identifier2 = "unicorn-400" @@ -261,7 +261,7 @@ final class ImageCacheTestCase: BaseTestCase { func testThatItCanFetchImageFromCacheWithRequestIdentifier() { // Given let image = self.image(forResource: "unicorn", withExtension: "png") - let request = try! URLRequest(url: "https://images.example.com/animals", method: .get) + let request = Endpoint.get.urlRequest let identifier = "unicorn" // When diff --git a/Tests/ImageDownloaderStressTests.swift b/Tests/ImageDownloaderStressTests.swift index ec9fb6cc..05034ccf 100644 --- a/Tests/ImageDownloaderStressTests.swift +++ b/Tests/ImageDownloaderStressTests.swift @@ -27,33 +27,34 @@ import Foundation import XCTest -class ImageDownloaderStressTestCase: BaseTestCase { - let imageCount = 10 - var kittenCache: Set = [] +final class ImageDownloaderStressTestCase: BaseTestCase { + let imageCount = 1000 + var cache: Set = [] // MARK: - Setup and Teardown override func setUp() { super.setUp() - kittenCache.removeAll() + + cache.removeAll() } // MARK: - Tests - Common Use Cases func testThatItCanDownloadManyImagesInParallel() { // Given - let imageRequests = (1...imageCount).map { _ in randomKittenURLRequest() } + let endpoints = (1...imageCount).map { _ in randomUniversalImageEndpoint() } let imageDownloader = ImageDownloader(configuration: .ephemeral) let expect = expectation(description: "all requests should complete") - expect.expectedFulfillmentCount = imageRequests.count + expect.expectedFulfillmentCount = endpoints.count var receipts: [RequestReceipt] = [] var responses: [AFIDataResponse] = [] // When - for imageRequest in imageRequests { - let receipt = imageDownloader.download(imageRequest, completion: { response in + for endpoint in endpoints { + let receipt = imageDownloader.download(endpoint, completion: { response in responses.append(response) expect.fulfill() }) @@ -72,18 +73,18 @@ class ImageDownloaderStressTestCase: BaseTestCase { func testThatItCanDownloadManyImagesInParallelWhileCancellingRequests() { // Given let cancelledImageCount = 4 - let imageRequests = (1...imageCount).map { _ in randomKittenURLRequest() } + let endpoints = (1...imageCount).map { _ in randomUniversalImageEndpoint() } let imageDownloader = ImageDownloader(configuration: .ephemeral) let expect = expectation(description: "all requests should complete") - expect.expectedFulfillmentCount = imageRequests.count + expect.expectedFulfillmentCount = endpoints.count var receipts: [RequestReceipt] = [] var responses: [AFIDataResponse] = [] // When - for imageRequest in imageRequests { - let receipt = imageDownloader.download(imageRequest, completion: { response in + for endpoint in endpoints { + let receipt = imageDownloader.download(endpoint, completion: { response in responses.append(response) expect.fulfill() }) @@ -110,18 +111,18 @@ class ImageDownloaderStressTestCase: BaseTestCase { func testThatItCanDownloadManyImagesInParallelWhileResumingRequestsExternally() { // Given - let imageRequests = (1...imageCount).map { _ in randomKittenURLRequest() } + let endpoints = (1...imageCount).map { _ in randomUniversalImageEndpoint() } let imageDownloader = ImageDownloader(configuration: .ephemeral) let expect = expectation(description: "all requests should complete") - expect.expectedFulfillmentCount = imageRequests.count + expect.expectedFulfillmentCount = endpoints.count var receipts: [RequestReceipt] = [] var responses: [AFIDataResponse] = [] // When - for imageRequest in imageRequests { - let receipt = imageDownloader.download(imageRequest, completion: { response in + for endpoint in endpoints { + let receipt = imageDownloader.download(endpoint, completion: { response in responses.append(response) expect.fulfill() }) @@ -142,18 +143,18 @@ class ImageDownloaderStressTestCase: BaseTestCase { func testThatItCanDownloadManyImagesInParallelWhileCancellingRequestsExternally() { // Given let cancelledImageCount = 4 - let imageRequests = (1...imageCount).map { _ in randomKittenURLRequest() } + let endpoints = (1...imageCount).map { _ in randomUniversalImageEndpoint() } let imageDownloader = ImageDownloader(configuration: .ephemeral) let expect = expectation(description: "all requests should complete") - expect.expectedFulfillmentCount = imageRequests.count + expect.expectedFulfillmentCount = endpoints.count var receipts: [RequestReceipt] = [] var responses: [AFIDataResponse] = [] // When - for imageRequest in imageRequests { - let receipt = imageDownloader.download(imageRequest, completion: { response in + for endpoint in endpoints { + let receipt = imageDownloader.download(endpoint, completion: { response in responses.append(response) expect.fulfill() }) @@ -176,24 +177,17 @@ class ImageDownloaderStressTestCase: BaseTestCase { XCTAssertEqual(failureCount, cancelledImageCount) } - private func randomKittenURLRequest() -> URLRequest { - let urlString = uniqueKittenURLString() - let url = URL(string: urlString)! - - return URLRequest(url: url) - } - - private func uniqueKittenURLString() -> String { - let width = Int.random(in: 100...400) - let height = Int.random(in: 100...400) + private func randomUniversalImageEndpoint() -> Endpoint { + let endpoint = Endpoint.image(Endpoint.Image.universalCases.randomElement()!) + .modifying(\.queryItems, to: [.init(name: "random", value: "\(arc4random())")]) - let urlString = "https://placekitten.com/\(width)/\(height)" + let urlString = endpoint.url.absoluteString - if kittenCache.contains(urlString) { - return uniqueKittenURLString() + if cache.contains(urlString) { + return randomUniversalImageEndpoint() } else { - kittenCache.insert(urlString) - return urlString + cache.insert(urlString) + return endpoint } } } diff --git a/Tests/ImageDownloaderTests.swift b/Tests/ImageDownloaderTests.swift index 0e02939c..4803c07b 100644 --- a/Tests/ImageDownloaderTests.swift +++ b/Tests/ImageDownloaderTests.swift @@ -24,10 +24,11 @@ @testable import Alamofire @testable import AlamofireImage + import Foundation import XCTest -private class ThreadCheckFilter: ImageFilter { +private final class ThreadCheckFilter: ImageFilter { var calledOnMainQueue = false init() {} @@ -44,7 +45,7 @@ private class ThreadCheckFilter: ImageFilter { #if os(iOS) || os(tvOS) -private class TestCircleFilter: ImageFilter { +private final class TestCircleFilter: ImageFilter { var filterOperationCompleted = false var filter: (Image) -> Image { @@ -59,11 +60,12 @@ private class TestCircleFilter: ImageFilter { // MARK: - -class ImageDownloaderTestCase: BaseTestCase { +final class ImageDownloaderTestCase: BaseTestCase { // MARK: - Setup and Teardown override func setUp() { super.setUp() + ImageDownloader.defaultURLCache().removeAllCachedResponses() } @@ -101,11 +103,10 @@ class ImageDownloaderTestCase: BaseTestCase { func testThatImageDownloaderCanBeInitializedAndDeinitializedWithActiveDownloads() { // Given - let urlRequest = try! URLRequest(url: "https://httpbin.org/image/png", method: .get) var downloader: ImageDownloader? = ImageDownloader() // When - _ = downloader?.download(urlRequest, completion: { _ in + _ = downloader?.download(.image(.png), completion: { _ in // No-op }) @@ -120,18 +121,17 @@ class ImageDownloaderTestCase: BaseTestCase { func testThatItCanDownloadAnImage() { // Given let downloader = ImageDownloader() - let urlRequest = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) let expectation = self.expectation(description: "image download should succeed") var response: AFIDataResponse? // When - downloader.download(urlRequest, completion: { closureResponse in + downloader.download(.image(.jpeg), completion: { closureResponse in response = closureResponse expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNotNil(response?.request, "request should not be nil") @@ -144,9 +144,6 @@ class ImageDownloaderTestCase: BaseTestCase { // Given let downloader = ImageDownloader() - let urlRequest1 = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) - let urlRequest2 = try! URLRequest(url: "https://httpbin.org/image/png", method: .get) - let expectation1 = expectation(description: "download 1 should succeed") let expectation2 = expectation(description: "download 2 should succeed") @@ -154,19 +151,19 @@ class ImageDownloaderTestCase: BaseTestCase { var result2: Result? // When - downloader.download(urlRequest1, completion: { closureResponse in + downloader.download(.image(.png), completion: { closureResponse in result1 = closureResponse.result expectation1.fulfill() }) - downloader.download(urlRequest2, completion: { closureResponse in + downloader.download(.image(.jpeg), completion: { closureResponse in result2 = closureResponse.result expectation2.fulfill() }) let activeRequestCount = downloader.activeRequestCount - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertEqual(activeRequestCount, 2, "active request count should be 2") @@ -182,9 +179,6 @@ class ImageDownloaderTestCase: BaseTestCase { // Given let downloader = ImageDownloader() - let urlRequest1 = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) - let urlRequest2 = try! URLRequest(url: "https://httpbin.org/image/png", method: .get) - let expectation = self.expectation(description: "both downloads should succeed") expectation.expectedFulfillmentCount = 2 @@ -192,7 +186,7 @@ class ImageDownloaderTestCase: BaseTestCase { var results: [AFIResult] = [] // When - downloader.download([urlRequest1, urlRequest2], filter: nil, completion: { closureResponse in + downloader.download([.image(.png), .image(.jpeg)], filter: nil, completion: { closureResponse in results.append(closureResponse.result) completedDownloads += 1 expectation.fulfill() @@ -200,7 +194,7 @@ class ImageDownloaderTestCase: BaseTestCase { let activeRequestCount = downloader.activeRequestCount - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertEqual(activeRequestCount, 2, "active request count should be 2") @@ -213,15 +207,12 @@ class ImageDownloaderTestCase: BaseTestCase { // Given let downloader = ImageDownloader(maximumActiveDownloads: 1) - let urlRequest1 = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) - let urlRequest2 = try! URLRequest(url: "https://httpbin.org/image/png", method: .get) - // When - let requestReceipt1 = downloader.download(urlRequest1, completion: { _ in + let requestReceipt1 = downloader.download(.image(.png), completion: { _ in // No-op }) - let requestReceipt2 = downloader.download(urlRequest2, completion: { _ in + let requestReceipt2 = downloader.download(.image(.jpeg), completion: { _ in // No-op }) @@ -236,18 +227,17 @@ class ImageDownloaderTestCase: BaseTestCase { func testThatItCallsTheCompletionHandlerEvenWhenDownloadFails() { // Given let downloader = ImageDownloader() - let urlRequest = try! URLRequest(url: "https://httpbin.org/get", method: .get) let expectation = self.expectation(description: "download request should fail") var response: AFIDataResponse? // When - downloader.download(urlRequest, completion: { closureResponse in + downloader.download(.get, completion: { closureResponse in response = closureResponse expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNotNil(response?.request, "request should not be nil") @@ -269,7 +259,7 @@ class ImageDownloaderTestCase: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNil(response?.request, "request should not be nil") @@ -281,7 +271,7 @@ class ImageDownloaderTestCase: BaseTestCase { // Given let downloader: ImageDownloader = { let configuration: URLSessionConfiguration = { - let configuration = URLSessionConfiguration.default + let configuration = URLSessionConfiguration.af.default configuration.urlCache = nil return configuration @@ -291,18 +281,17 @@ class ImageDownloaderTestCase: BaseTestCase { return downloader }() - let urlRequest = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) let expectation = self.expectation(description: "image download should succeed") var response: AFIDataResponse? // When - downloader.download(urlRequest, completion: { closureResponse in + downloader.download(.image(.jpeg), completion: { closureResponse in response = closureResponse expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNotNil(response?.request, "request should not be nil") @@ -317,7 +306,6 @@ class ImageDownloaderTestCase: BaseTestCase { func testThatItCanDownloadImageAndApplyImageFilter() { // Given let downloader = ImageDownloader() - let urlRequest = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) let scaledSize = CGSize(width: 100, height: 60) let filter = ScaledToSizeFilter(size: scaledSize) @@ -326,12 +314,12 @@ class ImageDownloaderTestCase: BaseTestCase { var response: AFIDataResponse? // When - downloader.download(urlRequest, filter: filter, completion: { closureResponse in + downloader.download(.image(.jpeg), filter: filter, completion: { closureResponse in response = closureResponse expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNotNil(response?.request, "request should not be nil") @@ -347,9 +335,6 @@ class ImageDownloaderTestCase: BaseTestCase { // Given let downloader = ImageDownloader() - let urlRequest1 = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) - let urlRequest2 = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) - let filter1 = ScaledToSizeFilter(size: CGSize(width: 50, height: 50)) let filter2 = ScaledToSizeFilter(size: CGSize(width: 75, height: 75)) @@ -360,17 +345,17 @@ class ImageDownloaderTestCase: BaseTestCase { var result2: AFIResult? // When - let requestReceipt1 = downloader.download(urlRequest1, filter: filter1, completion: { closureResponse in + let requestReceipt1 = downloader.download(.image(.jpeg), filter: filter1, completion: { closureResponse in result1 = closureResponse.result expectation1.fulfill() }) - let requestReceipt2 = downloader.download(urlRequest2, filter: filter2, completion: { closureResponse in + let requestReceipt2 = downloader.download(.image(.jpeg), filter: filter2, completion: { closureResponse in result2 = closureResponse.result expectation2.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertEqual(requestReceipt1?.request.task, requestReceipt2?.request.task, "request 1 and 2 should be equal") @@ -394,9 +379,6 @@ class ImageDownloaderTestCase: BaseTestCase { // Given let downloader = ImageDownloader() - let urlRequest1 = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) - let urlRequest2 = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) - let filter1 = TestCircleFilter() let filter2 = TestCircleFilter() @@ -407,17 +389,17 @@ class ImageDownloaderTestCase: BaseTestCase { var result2: AFIResult? // When - let requestReceipt1 = downloader.download(urlRequest1, filter: filter1, completion: { closureResponse in + let requestReceipt1 = downloader.download(.image(.jpeg), filter: filter1, completion: { closureResponse in result1 = closureResponse.result expectation1.fulfill() }) - let requestReceipt2 = downloader.download(urlRequest2, filter: filter2, completion: { closureResponse in + let requestReceipt2 = downloader.download(.image(.jpeg), filter: filter2, completion: { closureResponse in result2 = closureResponse.result expectation2.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertEqual(requestReceipt1?.request.task, requestReceipt2?.request.task, "tasks 1 and 2 should be equal") @@ -439,7 +421,6 @@ class ImageDownloaderTestCase: BaseTestCase { func testThatItCallsTheProgressHandlerOnTheMainQueueByDefault() { // Given let downloader = ImageDownloader() - let urlRequest = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) let progressExpectation = expectation(description: "progress closure should be called") let completedExpectation = expectation(description: "download request should succeed") @@ -448,7 +429,7 @@ class ImageDownloaderTestCase: BaseTestCase { var calledOnMainQueue = false // When - downloader.download(urlRequest, + downloader.download(.image(.jpeg), progress: { _ in if progressCalled == false { progressCalled = true @@ -460,7 +441,7 @@ class ImageDownloaderTestCase: BaseTestCase { completedExpectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(calledOnMainQueue, "progress handler should be called on main queue") @@ -469,7 +450,6 @@ class ImageDownloaderTestCase: BaseTestCase { func testThatItCallsTheProgressHandlerOnTheProgressQueue() { // Given let downloader = ImageDownloader() - let urlRequest = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) let progressExpectation = expectation(description: "progress closure should be called") let completedExpectation = expectation(description: "download request should succeed") @@ -478,7 +458,7 @@ class ImageDownloaderTestCase: BaseTestCase { var calledOnExpectedQueue = false // When - downloader.download(urlRequest, + downloader.download(.image(.jpeg), progress: { _ in if progressCalled == false { progressCalled = true @@ -492,7 +472,7 @@ class ImageDownloaderTestCase: BaseTestCase { completedExpectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(calledOnExpectedQueue, "progress handler should be called on expected queue") @@ -503,21 +483,20 @@ class ImageDownloaderTestCase: BaseTestCase { func testThatCancellingDownloadCallsCompletionWithCancellationError() { // Given let downloader = ImageDownloader() - let urlRequest = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) let expectation = self.expectation(description: "download request should cancel") var response: AFIDataResponse? // When - let requestReceipt = downloader.download(urlRequest, completion: { closureResponse in + let requestReceipt = downloader.download(.image(.jpeg), completion: { closureResponse in response = closureResponse expectation.fulfill() }) downloader.cancelRequest(with: requestReceipt!) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNotNil(response, "response should not be nil") @@ -536,9 +515,6 @@ class ImageDownloaderTestCase: BaseTestCase { // Given let downloader = ImageDownloader() - let urlRequest1 = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) - let urlRequest2 = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) - let expectation1 = expectation(description: "download request 1 should succeed") let expectation2 = expectation(description: "download request 2 should succeed") @@ -546,25 +522,24 @@ class ImageDownloaderTestCase: BaseTestCase { var response2: AFIDataResponse? // When - let requestReceipt1 = downloader.download(urlRequest1, completion: { closureResponse in + let requestReceipt1 = downloader.download(.image(.jpeg), completion: { closureResponse in response1 = closureResponse expectation1.fulfill() }) - let requestReceipt2 = downloader.download(urlRequest2, completion: { closureResponse in + let requestReceipt2 = downloader.download(.image(.jpeg), completion: { closureResponse in response2 = closureResponse expectation2.fulfill() }) downloader.cancelRequest(with: requestReceipt1!) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertEqual(requestReceipt1?.request.task, requestReceipt2?.request.task, "tasks 1 and 2 should be equal") XCTAssertNotNil(response1, "response 1 should not be nil") - XCTAssertNotNil(response1?.request, "response 1 request should not be nil") XCTAssertNil(response1?.response, "response 1 response should be nil") XCTAssertNil(response1?.data, "response 1 data should be nil") XCTAssertTrue(response1?.result.isFailure ?? false, "response 1 result should be a failure case") @@ -586,17 +561,17 @@ class ImageDownloaderTestCase: BaseTestCase { // Given let downloader = ImageDownloader() - let imageRequests: [URLRequest] = ["https://secure.gravatar.com/avatar/5a105e8b9d40e1329780d62ea2265d8a?d=identicon", - "https://secure.gravatar.com/avatar/6a105e8b9d40e1329780d62ea2265d8a?d=identicon", - "https://secure.gravatar.com/avatar/7a105e8b9d40e1329780d62ea2265d8a?d=identicon", - "https://secure.gravatar.com/avatar/8a105e8b9d40e1329780d62ea2265d8a?d=identicon", - "https://secure.gravatar.com/avatar/9a105e8b9d40e1329780d62ea2265d8a?d=identicon"].map { URLRequest(url: URL(string: $0)!) } + let imageEndpoints: [Endpoint] = ["5a105e8b9d40e1329780d62ea2265d8a", + "6a105e8b9d40e1329780d62ea2265d8a", + "7a105e8b9d40e1329780d62ea2265d8a", + "8a105e8b9d40e1329780d62ea2265d8a", + "9a105e8b9d40e1329780d62ea2265d8a"].map(Endpoint.gravatar) var initialResults: [AFIResult] = [] var finalResults: [AFIResult] = [] // When - for (index, imageRequest) in imageRequests.enumerated() { + for (index, imageRequest) in imageEndpoints.enumerated() { let expectation = self.expectation(description: "Download \(index) should be cancelled: \(imageRequest)") let receipt = downloader.download(imageRequest, completion: { response in @@ -615,7 +590,7 @@ class ImageDownloaderTestCase: BaseTestCase { } } - for (index, imageRequest) in imageRequests.enumerated() { + for (index, imageRequest) in imageEndpoints.enumerated() { let expectation = self.expectation(description: "Download \(index) should complete: \(imageRequest)") downloader.download(imageRequest, completion: { response in @@ -630,7 +605,7 @@ class ImageDownloaderTestCase: BaseTestCase { }) } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertEqual(initialResults.count, 5) @@ -656,10 +631,9 @@ class ImageDownloaderTestCase: BaseTestCase { func testThatItDoesNotAttachAuthenticationCredentialToRequestIfItDoesNotExist() { // Given let downloader = ImageDownloader() - let urlRequest = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) // When - let requestReceipt = downloader.download(urlRequest, completion: { _ in + let requestReceipt = downloader.download(.image(.jpeg), completion: { _ in // No-op }) @@ -670,15 +644,14 @@ class ImageDownloaderTestCase: BaseTestCase { XCTAssertNil(credential, "credential should be nil") } - func testThatItAttachsUsernamePasswordCredentialToRequestIfItExists() { + func testThatItAttachesUsernamePasswordCredentialToRequestIfItExists() { // Given let downloader = ImageDownloader() - let urlRequest = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) // When downloader.addAuthentication(user: "foo", password: "bar") - let requestReceipt = downloader.download(urlRequest, completion: { _ in + let requestReceipt = downloader.download(.image(.jpeg), completion: { _ in // No-op }) @@ -689,16 +662,15 @@ class ImageDownloaderTestCase: BaseTestCase { XCTAssertNotNil(credential, "credential should not be nil") } - func testThatItAttachsAuthenticationCredentialToRequestIfItExists() { + func testThatItAttachesAuthenticationCredentialToRequestIfItExists() { // Given let downloader = ImageDownloader() - let urlRequest = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) // When let credential = URLCredential(user: "foo", password: "bar", persistence: .forSession) downloader.addAuthentication(usingCredential: credential) - let requestReceipt = downloader.download(urlRequest, completion: { _ in + let requestReceipt = downloader.download(.image(.jpeg), completion: { _ in // No-op }) @@ -726,7 +698,7 @@ class ImageDownloaderTestCase: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(calledOnMainQueue, "completion handler should be called on main queue") @@ -735,19 +707,18 @@ class ImageDownloaderTestCase: BaseTestCase { func testThatItCallsTheCompletionHandlerOnTheMainQueueIfRequestFailed() { // Given let downloader = ImageDownloader() - let urlRequest = try! URLRequest(url: "does-not-exist", method: .get) let expectation = self.expectation(description: "download request should succeed") var calledOnMainQueue = false // When - downloader.download(urlRequest, completion: { _ in + downloader.download(.nonexistent, completion: { _ in calledOnMainQueue = Thread.isMainThread expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(calledOnMainQueue, "completion handler should be called on main queue") @@ -768,7 +739,7 @@ class ImageDownloaderTestCase: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(calledOnMainQueue, "completion handler should be called on main queue") @@ -777,17 +748,16 @@ class ImageDownloaderTestCase: BaseTestCase { func testThatItNeverCallsTheImageFilterOnTheMainQueue() { // Given let downloader = ImageDownloader() - let urlRequest = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) let filter = ThreadCheckFilter() let expectation = self.expectation(description: "download request should succeed") // When - downloader.download(urlRequest, filter: filter, completion: { _ in + downloader.download(.image(.jpeg), filter: filter, completion: { _ in expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertFalse(filter.calledOnMainQueue, "filter should not be called on main queue") @@ -798,27 +768,25 @@ class ImageDownloaderTestCase: BaseTestCase { func testThatCachedImageIsReturnedIfAllowedByCachePolicy() { // Given let downloader = ImageDownloader() - let urlRequest1 = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) let expectation1 = expectation(description: "image download should succeed") // When - let requestReceipt1 = downloader.download(urlRequest1, completion: { _ in + let requestReceipt1 = downloader.download(.image(.jpeg), completion: { _ in expectation1.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) - var urlRequest2 = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) - urlRequest2.cachePolicy = .returnCacheDataElseLoad + let endpoint = Endpoint.image(.jpeg).modifying(\.cachePolicy, to: .returnCacheDataElseLoad) let expectation2 = expectation(description: "image download should succeed") - let requestReceipt2 = downloader.download(urlRequest2, completion: { _ in + let requestReceipt2 = downloader.download(endpoint, completion: { _ in expectation2.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNotNil(requestReceipt1, "request receipt 1 should not be nil") @@ -828,27 +796,25 @@ class ImageDownloaderTestCase: BaseTestCase { func testThatCachedImageIsNotReturnedIfNotAllowedByCachePolicy() { // Given let downloader = ImageDownloader() - let urlRequest1 = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) let expectation1 = expectation(description: "image download should succeed") // When - let requestReceipt1 = downloader.download(urlRequest1, completion: { _ in + let requestReceipt1 = downloader.download(.image(.jpeg), completion: { _ in expectation1.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) - var urlRequest2 = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) - urlRequest2.cachePolicy = .reloadIgnoringLocalCacheData + let endpoint = Endpoint.image(.jpeg).modifying(\.cachePolicy, to: .reloadIgnoringLocalCacheData) let expectation2 = expectation(description: "image download should succeed") - let requestReceipt2 = downloader.download(urlRequest2, completion: { _ in + let requestReceipt2 = downloader.download(endpoint, completion: { _ in expectation2.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNotNil(requestReceipt1, "request receipt 1 should not be nil") @@ -858,18 +824,17 @@ class ImageDownloaderTestCase: BaseTestCase { func testThatItCanDownloadImagesWhenNoImageCacheIsAvailable() { // Given let downloader = ImageDownloader(imageCache: nil) - let urlRequest = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) let expectation = self.expectation(description: "image download should succeed") var response: AFIDataResponse? // When - downloader.download(urlRequest, completion: { closureResponse in + downloader.download(.image(.jpeg), completion: { closureResponse in response = closureResponse expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNotNil(response?.request, "request should not be nil") @@ -880,7 +845,6 @@ class ImageDownloaderTestCase: BaseTestCase { func testThatItAutomaticallyCachesDownloadedImageIfCacheIsAvailable() { // Given let downloader = ImageDownloader() - let urlRequest = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) let expectation1 = expectation(description: "image download should succeed") @@ -888,21 +852,21 @@ class ImageDownloaderTestCase: BaseTestCase { var result2: AFIResult? // When - let requestReceipt1 = downloader.download(urlRequest, completion: { closureResponse in + let requestReceipt1 = downloader.download(.image(.jpeg), completion: { closureResponse in result1 = closureResponse.result expectation1.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) let expectation2 = expectation(description: "image download should succeed") - let requestReceipt2 = downloader.download(urlRequest, completion: { closureResponse in + let requestReceipt2 = downloader.download(.image(.jpeg), completion: { closureResponse in result2 = closureResponse.result expectation2.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNotNil(requestReceipt1, "request receipt 1 should not be nil") @@ -924,7 +888,6 @@ class ImageDownloaderTestCase: BaseTestCase { func testThatFilteredImageIsStoredInCacheIfCacheIsAvailable() { // Given let downloader = ImageDownloader() - let urlRequest = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) let size = CGSize(width: 20, height: 20) let filter = ScaledToSizeFilter(size: size) @@ -934,21 +897,21 @@ class ImageDownloaderTestCase: BaseTestCase { var result2: AFIResult? // When - let requestReceipt1 = downloader.download(urlRequest, filter: filter, completion: { closureResponse in + let requestReceipt1 = downloader.download(.image(.jpeg), filter: filter, completion: { closureResponse in result1 = closureResponse.result expectation1.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) let expectation2 = expectation(description: "image download should succeed") - let requestReceipt2 = downloader.download(urlRequest, filter: filter, completion: { closureResponse in + let requestReceipt2 = downloader.download(.image(.jpeg), filter: filter, completion: { closureResponse in result2 = closureResponse.result expectation2.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNotNil(requestReceipt1, "request receipt 1 should not be nil") @@ -976,8 +939,8 @@ class ImageDownloaderTestCase: BaseTestCase { func testThatStartingRequestIncrementsActiveRequestCount() { // Given let downloader = ImageDownloader() - let urlRequest = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) - let request = downloader.session.request(urlRequest) + let endpoint = Endpoint.image(.jpeg) + let request = downloader.session.request(endpoint) // When let activeRequestCountBefore = downloader.activeRequestCount @@ -995,11 +958,8 @@ class ImageDownloaderTestCase: BaseTestCase { // Given let downloader = ImageDownloader() - let urlRequest1 = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) - let urlRequest2 = try! URLRequest(url: "https://httpbin.org/image/png", method: .get) - - let request1 = downloader.session.request(urlRequest1) - let request2 = downloader.session.request(urlRequest2) + let request1 = downloader.session.request(.image(.jpeg)) + let request2 = downloader.session.request(.image(.png)) // When downloader.enqueue(request1) @@ -1017,11 +977,8 @@ class ImageDownloaderTestCase: BaseTestCase { // Given let downloader = ImageDownloader(downloadPrioritization: .lifo) - let urlRequest1 = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) - let urlRequest2 = try! URLRequest(url: "https://httpbin.org/image/png", method: .get) - - let request1 = downloader.session.request(urlRequest1) - let request2 = downloader.session.request(urlRequest2) + let request1 = downloader.session.request(.image(.jpeg)) + let request2 = downloader.session.request(.image(.png)) // When downloader.enqueue(request1) @@ -1039,11 +996,8 @@ class ImageDownloaderTestCase: BaseTestCase { // Given let downloader = ImageDownloader(downloadPrioritization: .fifo) - let urlRequest1 = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) - let urlRequest2 = try! URLRequest(url: "https://httpbin.org/image/png", method: .get) - - let request1 = downloader.session.request(urlRequest1) - let request2 = downloader.session.request(urlRequest2) + let request1 = downloader.session.request(.image(.jpeg)) + let request2 = downloader.session.request(.image(.png)) // When downloader.enqueue(request1) diff --git a/Tests/ImageFilterTests.swift b/Tests/ImageFilterTests.swift index 81f8d113..91417efe 100644 --- a/Tests/ImageFilterTests.swift +++ b/Tests/ImageFilterTests.swift @@ -29,7 +29,7 @@ import Foundation import UIKit import XCTest -class ImageFilterTestCase: BaseTestCase { +final class ImageFilterTestCase: BaseTestCase { let squareSize = CGSize(width: 50, height: 50) let largeSquareSize = CGSize(width: 100, height: 100) let scale = Int(round(UIScreen.main.scale)) diff --git a/Tests/RequestTests.swift b/Tests/RequestTests.swift index 499b6917..80d915ec 100644 --- a/Tests/RequestTests.swift +++ b/Tests/RequestTests.swift @@ -27,7 +27,7 @@ import Alamofire import Foundation import XCTest -class DataRequestTestCase: BaseTestCase { +final class DataRequestTestCase: BaseTestCase { var acceptableImageContentTypes: Set! // MARK: - Setup and Teardown @@ -70,116 +70,88 @@ class DataRequestTestCase: BaseTestCase { // MARK: - Tests - Image Serialization - func testThatImageResponseSerializerCanDownloadPNGImage() { - // Given - let urlString = "https://httpbin.org/image/png" - let expectation = self.expectation(description: "Request should return PNG response image") + func testThatImageResponseSerializerCanDownloadAllUniversalImageTypes() { + func download(_ imageType: Endpoint.Image) { + // Given + let expectation = self.expectation(description: "Request should return \(imageType.rawValue) response image") - var response: AFDataResponse? + var response: AFDataResponse? - // When - session.request(urlString) - .responseImage { closureResponse in - response = closureResponse - expectation.fulfill() - } + // When + // Automatically inflates on supported platforms. + session.request(.image(imageType)) + .responseImage { closureResponse in + response = closureResponse + expectation.fulfill() + } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) - // Then - XCTAssertNotNil(response?.request, "request should not be nil") - XCTAssertNotNil(response?.response, "response should not be nil") - XCTAssertTrue(response?.result.isSuccess ?? false, "result should be success") + // Then + XCTAssertNotNil(response?.request, "request should not be nil") + XCTAssertNotNil(response?.response, "response should not be nil") + XCTAssertTrue(response?.result.isSuccess ?? false, "result should be success") - if let image = response?.result.value { - #if os(iOS) - let screenScale = UIScreen.main.scale - let expectedSize = CGSize(width: CGFloat(100) / screenScale, height: CGFloat(100) / screenScale) - XCTAssertEqual(image.size, expectedSize, "image size does not match expected value") - XCTAssertEqual(image.scale, screenScale, "image scale does not match expected value") - #elseif os(macOS) - let expectedSize = CGSize(width: 100.0, height: 100.0) - XCTAssertEqual(image.size, expectedSize, "image size does not match expected value") - #endif - } else { - XCTFail("result image should not be nil") - } - } - - func testThatImageResponseSerializerCanDownloadJPGImage() { - // Given - let urlString = "https://httpbin.org/image/jpeg" - let expectation = self.expectation(description: "Request should return JPG response image") - - var response: AFDataResponse? - - // When - session.request(urlString) - .responseImage { closureResponse in - response = closureResponse - expectation.fulfill() + guard let image = response?.result.value else { + XCTFail("\(imageType.rawValue) image should not be nil") + return } - waitForExpectations(timeout: timeout, handler: nil) - - // Then - XCTAssertNotNil(response?.request, "request should not be nil") - XCTAssertNotNil(response?.response, "response should not be nil") - XCTAssertTrue(response?.result.isSuccess ?? false, "result should be success") - - if let image = response?.result.value { - #if os(iOS) - let screenScale = UIScreen.main.scale - let expectedSize = CGSize(width: CGFloat(239) / screenScale, height: CGFloat(178) / screenScale) - XCTAssertEqual(image.size, expectedSize, "image size does not match expected value") - XCTAssertEqual(image.scale, screenScale, "image scale does not match expected value") + let expectedSize = imageType.expectedSize + #if os(iOS) || os(tvOS) + XCTAssertEqual(image.size, expectedSize.scaledToScreen, "image size does not match expected value") + XCTAssertTrue(image.isScaledToScreen, "image scale does not match expected value") #elseif os(macOS) - let expectedSize = CGSize(width: 239.0, height: 178.0) XCTAssertEqual(image.size, expectedSize, "image size does not match expected value") #endif - } else { - XCTFail("result image should not be nil") } + + var images = Set(Endpoint.Image.allCases) + images.remove(.webp) // WebP is only supported on macOS 11+ and iOS 14+. + images.remove(.pdf) // No platform supports direct PDF downloads. + + images.forEach(download) } #if os(macOS) || os(iOS) // No WebP support on tvOS or watchOS. @available(macOS 11, iOS 14, *) func testThatImageResponseSerializerCanDownloadWebPImage() { + guard #available(macOS 11, iOS 14, *) else { return } + // Given - let urlString = "https://httpbin.org/image/webp" let expectation = self.expectation(description: "Request should return WebP response image") var response: AFDataResponse? // When - session.request(urlString) + session.request(.image(.webp)) .responseImage { closureResponse in response = closureResponse expectation.fulfill() } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNotNil(response?.request, "request should not be nil") XCTAssertNotNil(response?.response, "response should not be nil") XCTAssertTrue(response?.result.isSuccess ?? false, "result should be success") - if let image = response?.result.value { - #if os(iOS) - let screenScale = UIScreen.main.scale - let expectedSize = CGSize(width: CGFloat(274) / screenScale, height: CGFloat(367) / screenScale) - XCTAssertEqual(image.size, expectedSize, "image size does not match expected value") - XCTAssertEqual(image.scale, screenScale, "image scale does not match expected value") - #elseif os(macOS) - let expectedSize = CGSize(width: 274, height: 367) - XCTAssertEqual(image.size, expectedSize, "image size does not match expected value") - #endif - } else { - XCTFail("result image should not be nil") + guard let image = response?.result.value else { + XCTFail("WebP image should not be nil") + return } + + let expectedSize = Endpoint.Image.png.expectedSize + #if os(iOS) + XCTAssertEqual(image.size, expectedSize.scaledToScreen, "image size does not match expected value") + XCTAssertTrue(image.isScaledToScreen, "image scale does not match expected value") + #elseif os(macOS) + XCTAssertEqual(image.size, expectedSize, "image size does not match expected value") + #endif } + #endif func testThatImageResponseSerializerCanDownloadImageFromFileURL() { @@ -196,7 +168,7 @@ class DataRequestTestCase: BaseTestCase { expectation.fulfill() } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNotNil(response?.request, "request should not be nil") @@ -218,125 +190,22 @@ class DataRequestTestCase: BaseTestCase { } } - // MARK: - Tests - Image Inflation - - #if os(iOS) || os(tvOS) - func testThatImageResponseSerializerCanDownloadAndInflatePNGImage() { - // Given - let urlString = "https://httpbin.org/image/png" - let expectation = self.expectation(description: "Request should return PNG response image") - - var response: AFDataResponse? - - // When - session.request(urlString) - .responseImage { closureResponse in - response = closureResponse - expectation.fulfill() - } - - waitForExpectations(timeout: timeout, handler: nil) - - // Then - XCTAssertNotNil(response?.request, "request should not be nil") - XCTAssertNotNil(response?.response, "response should not be nil") - XCTAssertTrue(response?.result.isSuccess ?? false, "result should be success") - - if let image = response?.result.value { - let screenScale = UIScreen.main.scale - let expectedSize = CGSize(width: CGFloat(100) / screenScale, height: CGFloat(100) / screenScale) - - XCTAssertEqual(image.size, expectedSize, "image size does not match expected value") - XCTAssertEqual(image.scale, screenScale, "image scale does not match expected value") - } else { - XCTFail("result image should not be nil") - } - } - - func testThatImageResponseSerializerCanDownloadAndInflateJPGImage() { - // Given - let urlString = "https://httpbin.org/image/jpeg" - let expectation = self.expectation(description: "Request should return JPG response image") - - var response: AFDataResponse? - - // When - session.request(urlString) - .responseImage { closureResponse in - response = closureResponse - expectation.fulfill() - } - - waitForExpectations(timeout: timeout, handler: nil) - - // Then - XCTAssertNotNil(response?.request, "request should not be nil") - XCTAssertNotNil(response?.response, "response should not be nil") - XCTAssertTrue(response?.result.isSuccess ?? false, "result should be success") - - if let image = response?.result.value { - let screenScale = UIScreen.main.scale - let expectedSize = CGSize(width: CGFloat(239) / screenScale, height: CGFloat(178) / screenScale) - - XCTAssertEqual(image.size, expectedSize, "image size does not match expected value") - XCTAssertEqual(image.scale, screenScale, "image scale does not match expected value") - } else { - XCTFail("result image should not be nil") - } - } - #endif - - #if os(iOS) - func testThatImageResponseSerializerCanDownloadAndInflateWebPImage() { - // Given - let urlString = "https://httpbin.org/image/webp" - let expectation = self.expectation(description: "Request should return WebP response image") - - var response: AFDataResponse? - - // When - session.request(urlString) - .responseImage { closureResponse in - response = closureResponse - expectation.fulfill() - } - - waitForExpectations(timeout: timeout, handler: nil) - - // Then - XCTAssertNotNil(response?.request, "request should not be nil") - XCTAssertNotNil(response?.response, "response should not be nil") - XCTAssertTrue(response?.result.isSuccess ?? false, "result should be success") - - if let image = response?.result.value { - let screenScale = UIScreen.main.scale - let expectedSize = CGSize(width: CGFloat(274) / screenScale, height: CGFloat(367) / screenScale) - - XCTAssertEqual(image.size, expectedSize, "image size does not match expected value") - XCTAssertEqual(image.scale, screenScale, "image scale does not match expected value") - } else { - XCTFail("result image should not be nil") - } - } - #endif - // MARK: - Tests - Image Serialization Errors func testThatAttemptingToDownloadImageFromBadURLReturnsFailureResult() { // Given - let urlString = "https://invalid.for.sure" let expectation = self.expectation(description: "Request should fail with bad URL") var response: AFDataResponse? // When - session.request(urlString) + session.request(.nonexistent) .responseImage { closureResponse in response = closureResponse expectation.fulfill() } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNotNil(response?.request, "request should not be nil") @@ -347,19 +216,18 @@ class DataRequestTestCase: BaseTestCase { func testThatAttemptingToDownloadUnsupportedImageTypeReturnsFailureResult() { // Given - let urlString = "https://httpbin.org/image/svg" - let expectation = self.expectation(description: "Request should return svg response image") + let expectation = self.expectation(description: "Request should return pdf response image") var response: AFDataResponse? // When - session.request(urlString) + session.request(.image(.pdf)) .responseImage { closureResponse in response = closureResponse expectation.fulfill() } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNotNil(response?.request, "request should not be nil") @@ -376,19 +244,18 @@ class DataRequestTestCase: BaseTestCase { func testThatAttemptingToSerializeEmptyDataReturnsFailureResult() { // Given - let urlString = "https://httpbin.org/bytes/0" let expectation = self.expectation(description: "Request should download no bytes") var response: AFDataResponse? // When - session.request(urlString) + session.request(.bytes(0)) .responseImage { closureResponse in response = closureResponse expectation.fulfill() } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNotNil(response?.request, "request should not be nil") @@ -406,19 +273,18 @@ class DataRequestTestCase: BaseTestCase { func testThatAttemptingToSerializeRandomStreamDataReturnsFailureResult() { // Given let randomBytes = 4 * 1024 * 1024 - let urlString = "https://httpbin.org/bytes/\(randomBytes)" let expectation = self.expectation(description: "Request should download random bytes") var response: AFDataResponse? // When - session.request(urlString) + session.request(.bytes(randomBytes)) .responseImage { closureResponse in response = closureResponse expectation.fulfill() } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNotNil(response?.request, "request should not be nil") @@ -435,19 +301,18 @@ class DataRequestTestCase: BaseTestCase { func testThatAttemptingToSerializeJSONResponseIntoImageReturnsFailureResult() { // Given - let urlString = "https://httpbin.org/get" let expectation = self.expectation(description: "Request should return JSON") var response: AFDataResponse? // When - session.request(urlString) + session.request(.get) .responseImage { closureResponse in response = closureResponse expectation.fulfill() } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNotNil(response?.request, "request should not be nil") diff --git a/Tests/TestHelpers.swift b/Tests/TestHelpers.swift new file mode 100644 index 00000000..896bcb0c --- /dev/null +++ b/Tests/TestHelpers.swift @@ -0,0 +1,469 @@ +// +// TestHelpers.swift +// +// Copyright (c) 2021 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Alamofire +import AlamofireImage +import struct CoreGraphics.CGSize +import Foundation + +extension String { + static let nonexistentDomain = "https://nonexistent-domain.org" +} + +extension URL { + static let nonexistentDomain = URL(string: .nonexistentDomain)! +} + +struct Endpoint { + enum Scheme: String { + case http, https + + var port: Int { + switch self { + case .http: return 80 + case .https: return 443 + } + } + } + + enum Host: String { + case gravatar = "secure.gravatar.com" + case httpBin = "httpbin.org" + case localhost = "127.0.0.1" + case nonexistent = "nonexistent-domain.org" + + func port(for scheme: Scheme) -> Int { + switch self { + case .localhost: return 8080 + case .gravatar, .httpBin, .nonexistent: return scheme.port + } + } + } + + enum Path { + case basicAuth(username: String, password: String) + case bytes(count: Int) + case chunked(count: Int) + case compression(Compression) + case delay(interval: Int) + case digestAuth(qop: String = "auth", username: String, password: String) + case download(count: Int) + case gravatar(id: String) + case hiddenBasicAuth(username: String, password: String) + case image(Image) + case ip + case method(HTTPMethod) + case none + case payloads(count: Int) + case redirect(count: Int) + case redirectTo + case responseHeaders + case status(Int) + case stream(count: Int) + case xml + + var string: String { + switch self { + case let .basicAuth(username: username, password: password): + return "/basic-auth/\(username)/\(password)" + case let .bytes(count): + return "/bytes/\(count)" + case let .chunked(count): + return "/chunked/\(count)" + case let .compression(compression): + return "/\(compression.rawValue)" + case let .delay(interval): + return "/delay/\(interval)" + case let .digestAuth(qop, username, password): + return "/digest-auth/\(qop)/\(username)/\(password)" + case let .download(count): + return "/download/\(count)" + case let .gravatar(id): + return "/avatar/\(id)" + case let .hiddenBasicAuth(username, password): + return "/hidden-basic-auth/\(username)/\(password)" + case let .image(type): + return "/image/\(type.rawValue)" + case .ip: + return "/ip" + case let .method(method): + return "/\(method.rawValue.lowercased())" + case .none: + return "" + case let .payloads(count): + return "/payloads/\(count)" + case let .redirect(count): + return "/redirect/\(count)" + case .redirectTo: + return "/redirect-to" + case .responseHeaders: + return "/response-headers" + case let .status(code): + return "/status/\(code)" + case let .stream(count): + return "/stream/\(count)" + case .xml: + return "/xml" + } + } + } + + enum Image: String, CaseIterable { + static let universalCases: Set = { + var images = Set(Endpoint.Image.allCases) + images.remove(.webp) + images.remove(.pdf) + + return images + }() + + case bmp, jp2, jpeg, gif, heic, heif, pdf, png, webp + + var expectedSize: CGSize { + switch self { + case .bmp, .jp2, .jpeg, .gif, .pdf, .png, .webp: + return .init(width: 1, height: 1) + case .heic, .heif: + return .init(width: 64, height: 64) + } + } + } + + enum Compression: String { + case brotli, gzip, deflate + } + + static var get: Endpoint { method(.get) } + + static func basicAuth(forUser user: String = "user", password: String = "password") -> Endpoint { + Endpoint(path: .basicAuth(username: user, password: password)) + } + + static func bytes(_ count: Int) -> Endpoint { + Endpoint(path: .bytes(count: count)) + } + + static func chunked(_ count: Int) -> Endpoint { + Endpoint(path: .chunked(count: count)) + } + + static func compression(_ compression: Compression) -> Endpoint { + Endpoint(path: .compression(compression)) + } + + static var `default`: Endpoint { .get } + + static func delay(_ interval: Int) -> Endpoint { + Endpoint(path: .delay(interval: interval)) + } + + static func digestAuth(forUser user: String = "user", password: String = "password") -> Endpoint { + Endpoint(path: .digestAuth(username: user, password: password)) + } + + static func download(_ count: Int = 10_000, produceError: Bool = false) -> Endpoint { + Endpoint(path: .download(count: count), queryItems: [.init(name: "shouldProduceError", + value: "\(produceError)")]) + } + + static func gravatar(_ id: String) -> Endpoint { + Endpoint(scheme: .https, + host: .gravatar, + path: .gravatar(id: id), + queryItems: [.init(name: "d", value: "identicon")]) + } + + static func hiddenBasicAuth(forUser user: String = "user", password: String = "password") -> Endpoint { + Endpoint(path: .hiddenBasicAuth(username: user, password: password), + headers: [.authorization(username: user, password: password)]) + } + + static func image(_ type: Image) -> Endpoint { + Endpoint(path: .image(type)) + } + + static var ip: Endpoint { + Endpoint(path: .ip) + } + + static func method(_ method: HTTPMethod) -> Endpoint { + Endpoint(path: .method(method), method: method) + } + + static let nonexistent = Endpoint(host: .nonexistent) + + static func payloads(_ count: Int) -> Endpoint { + Endpoint(path: .payloads(count: count)) + } + + static func redirect(_ count: Int) -> Endpoint { + Endpoint(path: .redirect(count: count)) + } + + static func redirectTo(_ url: String, code: Int? = nil) -> Endpoint { + var items = [URLQueryItem(name: "url", value: url)] + items = code.map { items + [.init(name: "statusCode", value: "\($0)")] } ?? items + + return Endpoint(path: .redirectTo, queryItems: items) + } + + static func redirectTo(_ endpoint: Endpoint, code: Int? = nil) -> Endpoint { + var items = [URLQueryItem(name: "url", value: endpoint.url.absoluteString)] + items = code.map { items + [.init(name: "statusCode", value: "\($0)")] } ?? items + + return Endpoint(path: .redirectTo, queryItems: items) + } + + static var responseHeaders: Endpoint { + Endpoint(path: .responseHeaders) + } + + static func status(_ code: Int) -> Endpoint { + Endpoint(path: .status(code)) + } + + static func stream(_ count: Int) -> Endpoint { + Endpoint(path: .stream(count: count)) + } + + static var xml: Endpoint { + Endpoint(path: .xml, headers: [.contentType("application/xml")]) + } + + var scheme = Scheme.http + var port: Int { host.port(for: scheme) } + var host = Host.localhost + var path = Path.method(.get) + var method: HTTPMethod = .get + var headers: HTTPHeaders = .init() + var timeout: TimeInterval = 60 + var queryItems: [URLQueryItem] = [] + var cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy + + func modifying(_ keyPath: WritableKeyPath, to value: T) -> Endpoint { + var copy = self + copy[keyPath: keyPath] = value + + return copy + } +} + +extension Endpoint: URLRequestConvertible { + var urlRequest: URLRequest { try! asURLRequest() } + + func asURLRequest() throws -> URLRequest { + var request = URLRequest(url: try asURL()) + request.method = method + request.headers = headers + request.timeoutInterval = timeout + request.cachePolicy = cachePolicy + + return request + } +} + +extension Endpoint: URLConvertible { + var url: URL { try! asURL() } + + func asURL() throws -> URL { + var components = URLComponents() + components.scheme = scheme.rawValue + components.port = port + components.host = host.rawValue + components.path = path.string + + if !queryItems.isEmpty { + components.queryItems = queryItems + } + + return try components.asURL() + } +} + +extension Session { + func request(_ endpoint: Endpoint, + parameters: Parameters? = nil, + encoding: ParameterEncoding = URLEncoding.default, + headers: HTTPHeaders? = nil, + interceptor: RequestInterceptor? = nil, + requestModifier: RequestModifier? = nil) -> DataRequest { + request(endpoint as URLConvertible, + method: endpoint.method, + parameters: parameters, + encoding: encoding, + headers: headers, + interceptor: interceptor, + requestModifier: requestModifier) + } + + func request(_ endpoint: Endpoint, + parameters: Parameters? = nil, + encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default, + headers: HTTPHeaders? = nil, + interceptor: RequestInterceptor? = nil, + requestModifier: RequestModifier? = nil) -> DataRequest { + request(endpoint as URLConvertible, + method: endpoint.method, + parameters: parameters, + encoder: encoder, + headers: headers, + interceptor: interceptor, + requestModifier: requestModifier) + } + + func request(_ endpoint: Endpoint, interceptor: RequestInterceptor? = nil) -> DataRequest { + request(endpoint as URLRequestConvertible, interceptor: interceptor) + } + + func streamRequest(_ endpoint: Endpoint, + headers: HTTPHeaders? = nil, + automaticallyCancelOnStreamError: Bool = false, + interceptor: RequestInterceptor? = nil, + requestModifier: RequestModifier? = nil) -> DataStreamRequest { + streamRequest(endpoint as URLConvertible, + method: endpoint.method, + headers: headers, + automaticallyCancelOnStreamError: automaticallyCancelOnStreamError, + interceptor: interceptor, + requestModifier: requestModifier) + } + + func streamRequest(_ endpoint: Endpoint, + automaticallyCancelOnStreamError: Bool = false, + interceptor: RequestInterceptor? = nil) -> DataStreamRequest { + streamRequest(endpoint as URLRequestConvertible, + automaticallyCancelOnStreamError: automaticallyCancelOnStreamError, + interceptor: interceptor) + } + + func download(_ endpoint: Endpoint, + parameters: Parameters? = nil, + encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default, + headers: HTTPHeaders? = nil, + interceptor: RequestInterceptor? = nil, + requestModifier: RequestModifier? = nil, + to destination: DownloadRequest.Destination? = nil) -> DownloadRequest { + download(endpoint as URLConvertible, + method: endpoint.method, + parameters: parameters, + encoder: encoder, + headers: headers, + interceptor: interceptor, + requestModifier: requestModifier, + to: destination) + } + + func download(_ endpoint: Endpoint, + parameters: Parameters? = nil, + encoding: ParameterEncoding = URLEncoding.default, + headers: HTTPHeaders? = nil, + interceptor: RequestInterceptor? = nil, + requestModifier: RequestModifier? = nil, + to destination: DownloadRequest.Destination? = nil) -> DownloadRequest { + download(endpoint as URLConvertible, + method: endpoint.method, + parameters: parameters, + encoding: encoding, + headers: headers, + interceptor: interceptor, + requestModifier: requestModifier, + to: destination) + } + + func download(_ endpoint: Endpoint, + interceptor: RequestInterceptor? = nil, + to destination: DownloadRequest.Destination? = nil) -> DownloadRequest { + download(endpoint as URLRequestConvertible, interceptor: interceptor, to: destination) + } + + func upload(_ data: Data, + to endpoint: Endpoint, + headers: HTTPHeaders? = nil, + interceptor: RequestInterceptor? = nil, + fileManager: FileManager = .default, + requestModifier: RequestModifier? = nil) -> UploadRequest { + upload(data, to: endpoint as URLConvertible, + method: endpoint.method, + headers: headers, + interceptor: interceptor, + fileManager: fileManager, + requestModifier: requestModifier) + } +} + +extension ImageDownloader { + @discardableResult + func download(_ endpoint: Endpoint, + cacheKey: String? = nil, + receiptID: String = UUID().uuidString, + serializer: ImageResponseSerializer? = nil, + filter: ImageFilter? = nil, + progress: ProgressHandler? = nil, + progressQueue: DispatchQueue = DispatchQueue.main, + completion: CompletionHandler? = nil) -> RequestReceipt? { + download(endpoint as URLRequestConvertible, + cacheKey: cacheKey, + receiptID: receiptID, + serializer: serializer, + filter: filter, + progress: progress, + progressQueue: progressQueue, + completion: completion) + } + + @discardableResult + func download(_ endpoints: [Endpoint], + filter: ImageFilter? = nil, + progress: ProgressHandler? = nil, + progressQueue: DispatchQueue = DispatchQueue.main, + completion: CompletionHandler? = nil) + -> [RequestReceipt] { + download(endpoints as [URLRequestConvertible], + filter: filter, + progress: progress, + progressQueue: progressQueue, + completion: completion) + } +} + +extension Data { + var asString: String { + String(decoding: self, as: UTF8.self) + } +} + +struct TestResponse: Decodable { + let headers: [String: String] + let origin: String + let url: String? + let data: String? + let form: [String: String]? + let args: [String: String]? +} + +struct TestParameters: Encodable { + static let `default` = TestParameters(property: "property") + + let property: String +} diff --git a/Tests/UIButtonTests.swift b/Tests/UIButtonTests.swift index 67ba8e8a..f9ff9d5b 100644 --- a/Tests/UIButtonTests.swift +++ b/Tests/UIButtonTests.swift @@ -22,41 +22,45 @@ // THE SOFTWARE. // -#if !os(macOS) +#if !os(macOS) && !os(watchOS) @testable import Alamofire @testable import AlamofireImage import UIKit import XCTest -private class TestButton: UIButton { +private final class TestButton: UIButton { var imageObserver: (() -> Void)? required init(imageObserver: (() -> Void)? = nil) { self.imageObserver = imageObserver - super.init(frame: CGRect.zero) - } - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") + super.init(frame: CGRect.zero) } override func setBackgroundImage(_ image: UIImage?, for state: ControlState) { super.setBackgroundImage(image, for: state) + imageObserver?() } override func setImage(_ image: UIImage?, for state: ControlState) { super.setImage(image, for: state) + imageObserver?() } + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } } // MARK: - -class UIButtonTests: BaseTestCase { - let url = URL(string: "https://httpbin.org/image/jpeg")! +final class UIButtonTests: BaseTestCase { + let endpoint = Endpoint.image(.jpeg) + var url: URL { endpoint.url } // MARK: - Setup and Teardown @@ -82,7 +86,7 @@ class UIButtonTests: BaseTestCase { // When button.af.setImage(for: [], url: url) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageDownloadComplete) @@ -100,7 +104,7 @@ class UIButtonTests: BaseTestCase { // When button.af.setBackgroundImage(for: [], url: url) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(backgroundImageDownloadComplete) @@ -124,7 +128,7 @@ class UIButtonTests: BaseTestCase { }) // Then - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) XCTAssertNotNil(result?.value) } @@ -145,7 +149,7 @@ class UIButtonTests: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNotNil(result?.value) @@ -163,7 +167,7 @@ class UIButtonTests: BaseTestCase { // When button.af.setImage(for: [], url: url) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageDownloadComplete) @@ -182,7 +186,7 @@ class UIButtonTests: BaseTestCase { // When button.af.setBackgroundImage(for: [], url: url) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(backgroundImageDownloadComplete) @@ -203,11 +207,13 @@ class UIButtonTests: BaseTestCase { expectation1.fulfill() } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) let expectation2 = expectation(description: "background image should download successfully") var selectedStateImageDownloadComplete = false - url = URL(string: "https://httpbin.org/image/jpeg?random=\(arc4random())")! + url = Endpoint.image(.jpeg) + .modifying(\.queryItems, to: [.init(name: "random", value: "\(arc4random())")]) + .url button.af.setImage(for: [.selected], url: url) button.imageObserver = { @@ -215,11 +221,13 @@ class UIButtonTests: BaseTestCase { expectation2.fulfill() } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) let expectation3 = expectation(description: "background image should download successfully") var highlightedStateImageDownloadComplete = false - url = URL(string: "https://httpbin.org/image/jpeg?random=\(arc4random())")! + url = Endpoint.image(.jpeg) + .modifying(\.queryItems, to: [.init(name: "random", value: "\(arc4random())")]) + .url button.af.setImage(for: [.highlighted], url: url) button.imageObserver = { @@ -227,11 +235,13 @@ class UIButtonTests: BaseTestCase { expectation3.fulfill() } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) let expectation4 = expectation(description: "background image should download successfully") var disabledStateImageDownloadComplete = false - url = URL(string: "https://httpbin.org/image/jpeg?random=\(arc4random())")! + url = Endpoint.image(.jpeg) + .modifying(\.queryItems, to: [.init(name: "random", value: "\(arc4random())")]) + .url button.af.setImage(for: [.disabled], url: url) button.imageObserver = { @@ -239,7 +249,7 @@ class UIButtonTests: BaseTestCase { expectation4.fulfill() } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(normalStateImageDownloadComplete) @@ -269,10 +279,12 @@ class UIButtonTests: BaseTestCase { expectation1.fulfill() } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) let expectation2 = expectation(description: "background image should download successfully") var selectedStateBackgroundImageDownloadComplete = false - url = URL(string: "https://httpbin.org/image/jpeg?random=\(arc4random())")! + url = Endpoint.image(.jpeg) + .modifying(\.queryItems, to: [.init(name: "random", value: "\(arc4random())")]) + .url button.af.setBackgroundImage(for: [.selected], url: url) button.imageObserver = { @@ -280,11 +292,13 @@ class UIButtonTests: BaseTestCase { expectation2.fulfill() } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) let expectation3 = expectation(description: "background image should download successfully") var highlightedStateBackgroundImageDownloadComplete = false - url = URL(string: "https://httpbin.org/image/jpeg?random=\(arc4random())")! + url = Endpoint.image(.jpeg) + .modifying(\.queryItems, to: [.init(name: "random", value: "\(arc4random())")]) + .url button.af.setBackgroundImage(for: [.highlighted], url: url) button.imageObserver = { @@ -292,11 +306,13 @@ class UIButtonTests: BaseTestCase { expectation3.fulfill() } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) let expectation4 = expectation(description: "background image should download successfully") var disabledStateBackgroundImageDownloadComplete = false - url = URL(string: "https://httpbin.org/image/jpeg?random=\(arc4random())")! + url = Endpoint.image(.jpeg) + .modifying(\.queryItems, to: [.init(name: "random", value: "\(arc4random())")]) + .url button.af.setBackgroundImage(for: [.disabled], url: url) button.imageObserver = { @@ -304,7 +320,7 @@ class UIButtonTests: BaseTestCase { expectation4.fulfill() } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(normalStateBackgroundImageDownloadComplete) @@ -340,7 +356,7 @@ class UIButtonTests: BaseTestCase { button.af.setImage(for: [], url: url) let activeRequestCount = imageDownloader.activeRequestCount - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageDownloadComplete) @@ -365,7 +381,7 @@ class UIButtonTests: BaseTestCase { url: url, serializer: ImageResponseSerializer(imageScale: 4.0, inflateResponseImage: false)) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageDownloadComplete, "image download complete should be true") @@ -388,7 +404,7 @@ class UIButtonTests: BaseTestCase { url: url, serializer: ImageResponseSerializer(imageScale: 4.0, inflateResponseImage: false)) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageDownloadComplete, "image download complete should be true") @@ -410,7 +426,7 @@ class UIButtonTests: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // When button.af.setImage(for: [], url: url) @@ -439,14 +455,13 @@ class UIButtonTests: BaseTestCase { let button = UIButton() let downloader = ImageDownloader.default - let download = try! URLRequest(url: url.absoluteString, method: .get) let expectation = self.expectation(description: "image download should succeed") - downloader.download(download, filter: CircleFilter(), completion: { _ in + downloader.download(endpoint, filter: CircleFilter(), completion: { _ in expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // When button.af.setImage(for: .normal, url: url, filter: CircleFilter()) @@ -469,7 +484,7 @@ class UIButtonTests: BaseTestCase { // When button.af.setImage(for: .normal, url: url, cacheKey: cacheKey) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageCached, "image cached should be true") @@ -488,7 +503,7 @@ class UIButtonTests: BaseTestCase { // When button.af.setBackgroundImage(for: .normal, url: url, cacheKey: cacheKey) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageCached, "image cached should be true") @@ -516,7 +531,7 @@ class UIButtonTests: BaseTestCase { expectation.fulfill() } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageDownloadComplete) @@ -544,7 +559,7 @@ class UIButtonTests: BaseTestCase { expectation.fulfill() } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(backgroundImageDownloadComplete) @@ -565,7 +580,7 @@ class UIButtonTests: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // When button.af.setImage(for: [], url: url, placeholderImage: placeholderImage) @@ -588,7 +603,7 @@ class UIButtonTests: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // When button.af.setBackgroundImage(for: [], url: url, placeholderImage: placeholderImage) @@ -641,7 +656,7 @@ class UIButtonTests: BaseTestCase { // When button.af.setImage(for: .normal, url: url, filter: filter) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageDownloadComplete, "image download complete should be true") @@ -667,7 +682,7 @@ class UIButtonTests: BaseTestCase { // When button.af.setBackgroundImage(for: .normal, url: url, filter: filter) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageDownloadComplete, "image download complete should be true") @@ -685,7 +700,7 @@ class UIButtonTests: BaseTestCase { let button = UIButton() let urlRequest: URLRequest = { - var request = URLRequest(url: url) + var request = endpoint.urlRequest request.addValue("image/*", forHTTPHeaderField: "Accept") return request }() @@ -702,7 +717,7 @@ class UIButtonTests: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(completionHandlerCalled, "completion handler called should be true") @@ -715,7 +730,7 @@ class UIButtonTests: BaseTestCase { let button = UIButton() let urlRequest: URLRequest = { - var request = URLRequest(url: url) + var request = endpoint.urlRequest request.addValue("image/*", forHTTPHeaderField: "Accept") return request }() @@ -732,7 +747,7 @@ class UIButtonTests: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(completionHandlerCalled, "completion handler called should be true") @@ -743,7 +758,6 @@ class UIButtonTests: BaseTestCase { func testThatCompletionHandlerIsCalledWhenImageDownloadFails() { // Given let button = UIButton() - let urlRequest = URLRequest(url: URL(string: "really-bad-domain")!) let expectation = self.expectation(description: "image download should complete") @@ -751,13 +765,13 @@ class UIButtonTests: BaseTestCase { var result: AFIResult? // When - button.af.setImage(for: [], urlRequest: urlRequest, placeholderImage: nil, completion: { response in + button.af.setImage(for: [], urlRequest: Endpoint.nonexistent, placeholderImage: nil, completion: { response in completionHandlerCalled = true result = response.result expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(completionHandlerCalled, "completion handler called should be true") @@ -768,7 +782,6 @@ class UIButtonTests: BaseTestCase { func testThatCompletionHandlerIsCalledWhenBackgroundImageDownloadFails() { // Given let button = UIButton() - let urlRequest = URLRequest(url: URL(string: "really-bad-domain")!) let expectation = self.expectation(description: "image download should complete") @@ -776,13 +789,13 @@ class UIButtonTests: BaseTestCase { var result: AFIResult? // When - button.af.setBackgroundImage(for: [], urlRequest: urlRequest, placeholderImage: nil, completion: { response in + button.af.setBackgroundImage(for: [], urlRequest: Endpoint.nonexistent, placeholderImage: nil, completion: { response in completionHandlerCalled = true result = response.result expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(completionHandlerCalled, "completion handler called should be true") @@ -807,7 +820,7 @@ class UIButtonTests: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(completionHandlerCalled, "completion handler called should be true") @@ -832,7 +845,7 @@ class UIButtonTests: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(completionHandlerCalled, "completion handler called should be true") @@ -845,7 +858,6 @@ class UIButtonTests: BaseTestCase { func testThatImageDownloadCanBeCancelled() { // Given let button = UIButton() - let urlRequest = URLRequest(url: URL(string: "domain-name-does-not-exist")!) let expectation = self.expectation(description: "image download should succeed") @@ -854,7 +866,7 @@ class UIButtonTests: BaseTestCase { // When button.af.setImage(for: [], - urlRequest: urlRequest, + urlRequest: Endpoint.nonexistent, placeholderImage: nil, completion: { closureResponse in completionHandlerCalled = true @@ -863,7 +875,7 @@ class UIButtonTests: BaseTestCase { }) button.af.cancelImageRequest(for: []) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(completionHandlerCalled) @@ -874,7 +886,6 @@ class UIButtonTests: BaseTestCase { func testThatBackgroundImageDownloadCanBeCancelled() { // Given let button = UIButton() - let urlRequest = URLRequest(url: URL(string: "domain-name-does-not-exist")!) let expectation = self.expectation(description: "background image download should succeed") @@ -883,7 +894,7 @@ class UIButtonTests: BaseTestCase { // When button.af.setBackgroundImage(for: [], - urlRequest: urlRequest, + urlRequest: Endpoint.nonexistent, placeholderImage: nil, completion: { closureResponse in completionHandlerCalled = true @@ -892,7 +903,7 @@ class UIButtonTests: BaseTestCase { }) button.af.cancelBackgroundImageRequest(for: []) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(completionHandlerCalled) @@ -911,14 +922,14 @@ class UIButtonTests: BaseTestCase { // When button.af.setImage(for: [], - urlRequest: URLRequest(url: url), + urlRequest: endpoint, placeholderImage: nil, completion: { _ in completion1Called = true }) button.af.setImage(for: [], - urlRequest: URLRequest(url: URL(string: "https://httpbin.org/image/png")!), + urlRequest: Endpoint.image(.png), placeholderImage: nil, completion: { closureResponse in completion2Called = true @@ -926,7 +937,7 @@ class UIButtonTests: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(completion1Called) @@ -946,14 +957,14 @@ class UIButtonTests: BaseTestCase { // When button.af.setBackgroundImage(for: [], - urlRequest: URLRequest(url: url), + urlRequest: endpoint, placeholderImage: nil, completion: { _ in completion1Called = true }) button.af.setBackgroundImage(for: [], - urlRequest: URLRequest(url: URL(string: "https://httpbin.org/image/png")!), + urlRequest: Endpoint.image(.png), placeholderImage: nil, completion: { closureResponse in completion2Called = true @@ -961,7 +972,7 @@ class UIButtonTests: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(completion1Called) @@ -974,6 +985,7 @@ class UIButtonTests: BaseTestCase { // Given let button = UIButton() let expectation = self.expectation(description: "image download should succeed") + expectation.expectedFulfillmentCount = 2 var completion1Called = false var completion2Called = false @@ -981,16 +993,17 @@ class UIButtonTests: BaseTestCase { // When button.af.setImage(for: [], - urlRequest: URLRequest(url: url), + urlRequest: endpoint, placeholderImage: nil, completion: { _ in completion1Called = true + expectation.fulfill() }) button.af.cancelImageRequest(for: []) button.af.setImage(for: [], - urlRequest: URLRequest(url: url), + urlRequest: endpoint, placeholderImage: nil, completion: { closureResponse in completion2Called = true @@ -998,7 +1011,7 @@ class UIButtonTests: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(completion1Called) @@ -1018,7 +1031,7 @@ class UIButtonTests: BaseTestCase { // When button.af.setBackgroundImage(for: [], - urlRequest: URLRequest(url: url), + urlRequest: endpoint, placeholderImage: nil, completion: { _ in completion1Called = true @@ -1027,7 +1040,7 @@ class UIButtonTests: BaseTestCase { button.af.cancelBackgroundImageRequest(for: []) button.af.setBackgroundImage(for: [], - urlRequest: URLRequest(url: url), + urlRequest: endpoint, placeholderImage: nil, completion: { closureResponse in completion2Called = true @@ -1035,7 +1048,7 @@ class UIButtonTests: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(completion1Called) @@ -1054,7 +1067,7 @@ class UIButtonTests: BaseTestCase { // When button?.af.setImage(for: .normal, - urlRequest: URLRequest(url: url), + urlRequest: endpoint, completion: { [weak button] _ in completionCalled = true buttonReleased = button == nil @@ -1063,7 +1076,7 @@ class UIButtonTests: BaseTestCase { }) button = nil - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertEqual(completionCalled, true) @@ -1080,7 +1093,7 @@ class UIButtonTests: BaseTestCase { // When button?.af.setBackgroundImage(for: .normal, - urlRequest: URLRequest(url: url), + urlRequest: endpoint, completion: { [weak button] _ in completionCalled = true buttonReleased = button == nil @@ -1089,7 +1102,7 @@ class UIButtonTests: BaseTestCase { }) button = nil - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertEqual(completionCalled, true) @@ -1098,68 +1111,63 @@ class UIButtonTests: BaseTestCase { // MARK: - Redirects - // Disable redirect tests due to HTTPBin failures. -// func testThatImageBehindRedirectCanBeDownloaded() { -// // Given -// let redirectURLString = "https://httpbin.org/image/png" -// let url = URL(string: "https://httpbin.org/redirect-to?url=\(redirectURLString)")! -// -// let expectation = self.expectation(description: "image should download successfully") -// var imageDownloadComplete = false -// -// let button = TestButton { -// imageDownloadComplete = true -// expectation.fulfill() -// } -// -// // When -// button.af.setImage(for: [], url: url) -// waitForExpectations(timeout: timeout, handler: nil) -// -// // Then -// XCTAssertTrue(imageDownloadComplete, "image download complete should be true") -// XCTAssertNotNil(button.image(for: []), "button image should not be nil") -// } -// -// func testThatBackgroundImageBehindRedirectCanBeDownloaded() { -// // Given -// let redirectURLString = "https://httpbin.org/image/png" -// let url = URL(string: "https://httpbin.org/redirect-to?url=\(redirectURLString)")! -// -// let expectation = self.expectation(description: "image should download successfully") -// var backgroundImageDownloadComplete = false -// -// let button = TestButton { -// backgroundImageDownloadComplete = true -// expectation.fulfill() -// } -// -// // When -// button.af.setBackgroundImage(for: [], url: url) -// waitForExpectations(timeout: timeout, handler: nil) -// -// // Then -// XCTAssertTrue(backgroundImageDownloadComplete, "image download complete should be true") -// XCTAssertNotNil(button.backgroundImage(for: []), "button background image should not be nil") -// } + func testThatImageBehindRedirectCanBeDownloaded() { + // Given + let expectation = self.expectation(description: "image should download successfully") + var imageDownloadComplete = false - // MARK: - Accept Header + let button = TestButton { + imageDownloadComplete = true + expectation.fulfill() + } - func testThatAcceptHeaderMatchesAcceptableContentTypes() { + // When + button.af.setImage(for: [], url: Endpoint.redirectTo(.image(.png)).url) + waitForExpectations(timeout: timeout) + + // Then + XCTAssertTrue(imageDownloadComplete, "image download complete should be true") + XCTAssertNotNil(button.image(for: []), "button image should not be nil") + } + + func testThatBackgroundImageBehindRedirectCanBeDownloaded() { // Given - let button = UIButton() + let expectation = self.expectation(description: "image should download successfully") + var backgroundImageDownloadComplete = false + + let button = TestButton { + backgroundImageDownloadComplete = true + expectation.fulfill() + } // When - button.af.setImage(for: [], url: url) - let acceptField = button.af.imageRequestReceipt(for: [])?.request.request?.allHTTPHeaderFields?["Accept"] - button.af.cancelImageRequest(for: []) + button.af.setBackgroundImage(for: [], url: Endpoint.redirectTo(.image(.png)).url) + waitForExpectations(timeout: timeout) // Then - XCTAssertNotNil(acceptField) + XCTAssertTrue(backgroundImageDownloadComplete, "image download complete should be true") + XCTAssertNotNil(button.backgroundImage(for: []), "button background image should not be nil") + } + + // MARK: - Accept Header - if let acceptField = acceptField { - XCTAssertEqual(acceptField, ImageResponseSerializer.acceptableImageContentTypes.sorted().joined(separator: ",")) + func testThatAcceptHeaderMatchesAcceptableContentTypes() { + // Given + let expectation = self.expectation(description: "image should download successfully") + var acceptField: String? + + var button: TestButton? + button = TestButton { + acceptField = button?.af.imageRequestReceipt(for: [])?.request.request?.headers["Accept"] + expectation.fulfill() } + + // When + button?.af.setImage(for: [], url: url) + waitForExpectations(timeout: timeout) + + // Then + XCTAssertEqual(acceptField, ImageResponseSerializer.acceptableImageContentTypes.sorted().joined(separator: ",")) } } diff --git a/Tests/UIImage+AlamofireImageTests.swift b/Tests/UIImage+AlamofireImageTests.swift index b5a077d4..45b4d704 100644 --- a/Tests/UIImage+AlamofireImageTests.swift +++ b/Tests/UIImage+AlamofireImageTests.swift @@ -28,6 +28,12 @@ import Alamofire import UIKit +extension UIImage { + var isScaledToScreen: Bool { + scale == UIScreen.main.scale + } +} + extension AlamofireExtension where ExtendedType: UIImage { func isEqualToImage(_ image: UIImage, withinTolerance tolerance: UInt8 = 3) -> Bool { guard type.size.equalTo(image.size) else { return false } diff --git a/Tests/UIImageTests.swift b/Tests/UIImageTests.swift index 89f3a09d..bb7bce86 100644 --- a/Tests/UIImageTests.swift +++ b/Tests/UIImageTests.swift @@ -71,7 +71,7 @@ class UIImageTestCase: BaseTestCase { } } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then images.forEach { XCTAssertNotNil($0, "image should not be nil") } @@ -103,7 +103,7 @@ class UIImageTestCase: BaseTestCase { } } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then images.forEach { XCTAssertNotNil($0, "image should not be nil") } diff --git a/Tests/UIImageViewTests.swift b/Tests/UIImageViewTests.swift index dd66d7ca..1647bee1 100644 --- a/Tests/UIImageViewTests.swift +++ b/Tests/UIImageViewTests.swift @@ -30,7 +30,7 @@ import Foundation import UIKit import XCTest -private class TestImageView: UIImageView { +private final class TestImageView: UIImageView { var imageObserver: (() -> Void)? convenience init(imageObserver: (() -> Void)? = nil) { @@ -60,8 +60,9 @@ private class TestImageView: UIImageView { // MARK: - -class UIImageViewTestCase: BaseTestCase { - let url = URL(string: "https://httpbin.org/image/jpeg")! +final class UIImageViewTestCase: BaseTestCase { + let endpoint = Endpoint.image(.jpeg) + var url: URL { endpoint.url } // MARK: - Setup and Teardown @@ -87,7 +88,7 @@ class UIImageViewTestCase: BaseTestCase { // When imageView.af.setImage(withURL: url) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageDownloadComplete, "image download complete should be true") @@ -106,7 +107,7 @@ class UIImageViewTestCase: BaseTestCase { // When imageView.af.setImage(withURL: url) imageView.af.setImage(withURL: url) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageDownloadComplete, "image download complete should be true") @@ -125,7 +126,7 @@ class UIImageViewTestCase: BaseTestCase { // When imageView.af.setImage(withURL: url) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageDownloadComplete, "image download complete should be true") @@ -152,7 +153,7 @@ class UIImageViewTestCase: BaseTestCase { imageView.af.setImage(withURL: url) let activeRequestCount = imageDownloader.activeRequestCount - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageDownloadComplete, "image download complete should be true") @@ -176,7 +177,7 @@ class UIImageViewTestCase: BaseTestCase { imageView.af.setImage(withURL: url, serializer: ImageResponseSerializer(imageScale: 4.0, inflateResponseImage: false)) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageDownloadComplete, "image download complete should be true") @@ -191,14 +192,13 @@ class UIImageViewTestCase: BaseTestCase { let imageView = UIImageView() let downloader = ImageDownloader.default - let urlRequest = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) let expectation = self.expectation(description: "image download should succeed") - downloader.download(urlRequest, completion: { _ in + downloader.download(endpoint, completion: { _ in expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // When imageView.af.setImage(withURL: url) @@ -213,14 +213,13 @@ class UIImageViewTestCase: BaseTestCase { let imageView = UIImageView() let downloader = ImageDownloader.default - let urlRequest = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) let expectation = self.expectation(description: "image download should succeed") - downloader.download(urlRequest, filter: CircleFilter(), completion: { _ in + downloader.download(endpoint, filter: CircleFilter(), completion: { _ in expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // When imageView.af.setImage(withURL: url, filter: CircleFilter()) @@ -257,7 +256,7 @@ class UIImageViewTestCase: BaseTestCase { // When imageView.af.setImage(withURL: url, cacheKey: cacheKey) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageCached, "image cached should be true") @@ -285,7 +284,7 @@ class UIImageViewTestCase: BaseTestCase { expectation.fulfill() } - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageDownloadComplete, "image download complete should be true") @@ -312,13 +311,12 @@ class UIImageViewTestCase: BaseTestCase { let imageView = UIImageView() let downloader = ImageDownloader.default - let urlRequest = try! URLRequest(url: "https://httpbin.org/image/jpeg", method: .get) let expectation = self.expectation(description: "image download should succeed") - downloader.download(urlRequest, completion: { _ in + downloader.download(endpoint, completion: { _ in expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // When imageView.af.setImage(withURL: url, placeholderImage: placeholderImage) @@ -345,7 +343,7 @@ class UIImageViewTestCase: BaseTestCase { // When imageView.af.setImage(withURL: url, filter: filter) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageDownloadComplete, "image download complete should be true") @@ -370,7 +368,7 @@ class UIImageViewTestCase: BaseTestCase { // When imageView.af.setImage(withURL: url, placeholderImage: nil, filter: nil, imageTransition: .crossDissolve(0.5)) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageDownloadComplete, "image download complete should be true") @@ -386,43 +384,43 @@ class UIImageViewTestCase: BaseTestCase { let expectation1 = expectation(description: "image download should succeed") imageView.imageObserver = { expectation1.fulfill() } imageView.af.setImage(withURL: url, imageTransition: .noTransition) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) let expectation2 = expectation(description: "image download should succeed") imageView.imageObserver = { expectation2.fulfill() } ImageDownloader.default.imageCache?.removeAllImages() imageView.af.setImage(withURL: url, imageTransition: .crossDissolve(0.1)) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) let expectation3 = expectation(description: "image download should succeed") imageView.imageObserver = { expectation3.fulfill() } ImageDownloader.default.imageCache?.removeAllImages() imageView.af.setImage(withURL: url, imageTransition: .curlDown(0.1)) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) let expectation4 = expectation(description: "image download should succeed") imageView.imageObserver = { expectation4.fulfill() } ImageDownloader.default.imageCache?.removeAllImages() imageView.af.setImage(withURL: url, imageTransition: .curlUp(0.1)) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) let expectation5 = expectation(description: "image download should succeed") imageView.imageObserver = { expectation5.fulfill() } ImageDownloader.default.imageCache?.removeAllImages() imageView.af.setImage(withURL: url, imageTransition: .flipFromBottom(0.1)) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) let expectation6 = expectation(description: "image download should succeed") imageView.imageObserver = { expectation6.fulfill() } ImageDownloader.default.imageCache?.removeAllImages() imageView.af.setImage(withURL: url, imageTransition: .flipFromLeft(0.1)) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) let expectation7 = expectation(description: "image download should succeed") imageView.imageObserver = { expectation7.fulfill() } ImageDownloader.default.imageCache?.removeAllImages() imageView.af.setImage(withURL: url, imageTransition: .flipFromRight(0.1)) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) let expectation8 = expectation(description: "image download should succeed") imageView.imageObserver = { @@ -430,7 +428,7 @@ class UIImageViewTestCase: BaseTestCase { } ImageDownloader.default.imageCache?.removeAllImages() imageView.af.setImage(withURL: url, imageTransition: .flipFromTop(0.1)) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) let expectation9 = expectation(description: "image download should succeed") imageView.imageObserver = { @@ -443,7 +441,7 @@ class UIImageViewTestCase: BaseTestCase { animationOptions: [], animations: { $0.image = $1 }, completion: nil)) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(imageTransitionsComplete, "image transitions complete should be true") @@ -457,7 +455,7 @@ class UIImageViewTestCase: BaseTestCase { let imageView = UIImageView() let urlRequest: URLRequest = { - var request = URLRequest(url: url) + var request = endpoint.urlRequest request.addValue("image/*", forHTTPHeaderField: "Accept") return request }() @@ -478,7 +476,7 @@ class UIImageViewTestCase: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(completionHandlerCalled, "completion handler called should be true") @@ -489,7 +487,6 @@ class UIImageViewTestCase: BaseTestCase { func testThatCompletionHandlerIsCalledWhenImageDownloadFails() { // Given let imageView = UIImageView() - let urlRequest = URLRequest(url: URL(string: "domain-name-does-not-exist")!) let expectation = self.expectation(description: "image download should complete") @@ -497,7 +494,7 @@ class UIImageViewTestCase: BaseTestCase { var result: AFIResult? // When - imageView.af.setImage(withURLRequest: urlRequest, + imageView.af.setImage(withURLRequest: Endpoint.nonexistent, placeholderImage: nil, filter: nil, imageTransition: .noTransition, @@ -507,7 +504,7 @@ class UIImageViewTestCase: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(completionHandlerCalled, "completion handler called should be true") @@ -536,7 +533,7 @@ class UIImageViewTestCase: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(completionHandlerCalled, "completion handler called should be true") @@ -574,7 +571,7 @@ class UIImageViewTestCase: BaseTestCase { completionExpectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(completionHandlerCalled, "completion handler called should be true") @@ -587,7 +584,7 @@ class UIImageViewTestCase: BaseTestCase { // Given let imageView = UIImageView() let urlRequest: URLRequest = { - var request = URLRequest(url: url) + var request = endpoint.urlRequest request.addValue("image/*", forHTTPHeaderField: "Accept") return request }() @@ -599,7 +596,7 @@ class UIImageViewTestCase: BaseTestCase { downloadExpectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) let cachedExpectation = expectation(description: "image should be cached") var result: AFIResult? @@ -613,7 +610,7 @@ class UIImageViewTestCase: BaseTestCase { cachedExpectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertNotNil(result?.value, "result value should not be nil") @@ -625,7 +622,6 @@ class UIImageViewTestCase: BaseTestCase { func testThatImageDownloadCanBeCancelled() { // Given let imageView = UIImageView() - let urlRequest = URLRequest(url: URL(string: "domain-name-does-not-exist")!) let expectation = self.expectation(description: "image download should succeed") @@ -633,7 +629,7 @@ class UIImageViewTestCase: BaseTestCase { var result: AFIResult? // When - imageView.af.setImage(withURLRequest: urlRequest, + imageView.af.setImage(withURLRequest: Endpoint.nonexistent, placeholderImage: nil, filter: nil, imageTransition: .noTransition, @@ -644,7 +640,7 @@ class UIImageViewTestCase: BaseTestCase { }) imageView.af.cancelImageRequest() - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(completionHandlerCalled, "completion handler called should be true") @@ -662,7 +658,7 @@ class UIImageViewTestCase: BaseTestCase { var result: AFIResult? // When - imageView.af.setImage(withURLRequest: URLRequest(url: url), + imageView.af.setImage(withURLRequest: endpoint, placeholderImage: nil, filter: nil, imageTransition: .noTransition, @@ -670,7 +666,7 @@ class UIImageViewTestCase: BaseTestCase { completion1Called = true }) - imageView.af.setImage(withURLRequest: URLRequest(url: URL(string: "https://httpbin.org/image/png")!), + imageView.af.setImage(withURLRequest: Endpoint.image(.png), placeholderImage: nil, filter: nil, imageTransition: .noTransition, @@ -680,7 +676,7 @@ class UIImageViewTestCase: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(completion1Called, "completion 1 called should be true") @@ -699,7 +695,7 @@ class UIImageViewTestCase: BaseTestCase { var result: AFIResult? // When - imageView.af.setImage(withURLRequest: URLRequest(url: url), + imageView.af.setImage(withURLRequest: endpoint, placeholderImage: nil, filter: nil, imageTransition: .noTransition, @@ -709,7 +705,7 @@ class UIImageViewTestCase: BaseTestCase { imageView.af.cancelImageRequest() - imageView.af.setImage(withURLRequest: URLRequest(url: url), + imageView.af.setImage(withURLRequest: endpoint, placeholderImage: nil, filter: nil, imageTransition: .noTransition, @@ -719,7 +715,7 @@ class UIImageViewTestCase: BaseTestCase { expectation.fulfill() }) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertTrue(completion1Called, "completion 1 called should be true") @@ -737,7 +733,7 @@ class UIImageViewTestCase: BaseTestCase { var imageViewReleased: Bool? // When - imageView?.af.setImage(withURLRequest: URLRequest(url: url), + imageView?.af.setImage(withURLRequest: endpoint, completion: { [weak imageView] _ in completionCalled = true imageViewReleased = imageView == nil @@ -746,7 +742,7 @@ class UIImageViewTestCase: BaseTestCase { }) imageView = nil - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then XCTAssertEqual(completionCalled, true) @@ -755,38 +751,33 @@ class UIImageViewTestCase: BaseTestCase { // MARK: - Redirects - // Disable redirect tests due to HTTPBin failures. -// func testThatImageBehindRedirectCanBeDownloaded() { -// // Given -// let redirectURLString = "https://httpbin.org/image/png" -// let url = URL(string: "https://httpbin.org/redirect-to?url=\(redirectURLString)")! -// -// let expectation = self.expectation(description: "image should download successfully") -// var imageDownloadComplete = false -// -// let imageView = TestImageView { -// imageDownloadComplete = true -// expectation.fulfill() -// } -// -// // When -// imageView.af.setImage(withURL: url) -// waitForExpectations(timeout: timeout, handler: nil) -// -// // Then -// XCTAssertTrue(imageDownloadComplete, "image download complete should be true") -// XCTAssertNotNil(imageView.image, "image view image should not be nil") -// } + func testThatImageBehindRedirectCanBeDownloaded() { + // Given + let expectation = self.expectation(description: "image should download successfully") + var imageDownloadComplete = false + + let imageView = TestImageView { + imageDownloadComplete = true + expectation.fulfill() + } + + // When + imageView.af.setImage(withURL: Endpoint.redirectTo(.image(.png)).url) + waitForExpectations(timeout: timeout) + + // Then + XCTAssertTrue(imageDownloadComplete, "image download complete should be true") + XCTAssertNotNil(imageView.image, "image view image should not be nil") + } // MARK: - Accept Header func testThatAcceptHeaderMatchesAcceptableContentTypes() { // Given - var imageView: UIImageView? - let expectation = self.expectation(description: "image should download successfully") var acceptField: String? + var imageView: TestImageView? imageView = TestImageView { acceptField = imageView?.af.activeRequestReceipt?.request.request?.headers["Accept"] expectation.fulfill() @@ -794,15 +785,10 @@ class UIImageViewTestCase: BaseTestCase { // When imageView?.af.setImage(withURL: url) - waitForExpectations(timeout: timeout, handler: nil) + waitForExpectations(timeout: timeout) // Then - XCTAssertNotNil(acceptField) - - if let acceptField = acceptField { - print(acceptField) - XCTAssertEqual(acceptField, ImageResponseSerializer.acceptableImageContentTypes.sorted().joined(separator: ",")) - } + XCTAssertEqual(acceptField, ImageResponseSerializer.acceptableImageContentTypes.sorted().joined(separator: ",")) } } diff --git a/carthage.sh b/carthage.sh deleted file mode 100755 index 7a062998..00000000 --- a/carthage.sh +++ /dev/null @@ -1,19 +0,0 @@ -# carthage.sh -# Usage example: ./carthage.sh build --platform iOS - -set -euo pipefail - -xcconfig=$(mktemp /tmp/static.xcconfig.XXXXXX) -trap 'rm -f "$xcconfig"' INT TERM HUP EXIT - -# For Xcode 12 make sure EXCLUDED_ARCHS is set to arm architectures otherwise -# the build will fail on lipo due to duplicate architectures. - -CURRENT_XCODE_VERSION=$(xcodebuild -version | grep "Build version" | cut -d' ' -f3) -echo "EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200__BUILD_$CURRENT_XCODE_VERSION = arm64 arm64e armv7 armv7s armv6 armv8" >> $xcconfig - -echo 'EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200 = $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200__BUILD_$(XCODE_PRODUCT_BUILD_VERSION))' >> $xcconfig -echo 'EXCLUDED_ARCHS = $(inherited) $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_$(EFFECTIVE_PLATFORM_SUFFIX)__NATIVE_ARCH_64_BIT_$(NATIVE_ARCH_64_BIT)__XCODE_$(XCODE_VERSION_MAJOR))' >> $xcconfig - -export XCODE_XCCONFIG_FILE="$xcconfig" -carthage "$@"