Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable swift PM tests on Linux and macOS #118

Merged
merged 2 commits into from
May 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: "Mocker SPM CI"

on:
push:
branches:
- master
pull_request:
branches:
- '*'

jobs:
macos-run-tests:
name: Unit Tests (macOS)
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Run Tests
run: swift test

linux-run-tests:
name: Unit Tests (Linux)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run Tests
run: swift test
34 changes: 29 additions & 5 deletions Mocker.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
50D4606E20653F1F00A85D93 /* Mocker.h in Headers */ = {isa = PBXBuildFile; fileRef = 50D4606B20653F1F00A85D93 /* Mocker.h */; settings = {ATTRIBUTES = (Public, ); }; };
50D4607020653F2500A85D93 /* MockedData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D4605B20653EAF00A85D93 /* MockedData.swift */; };
50D4607120653F2700A85D93 /* MockerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D4605C20653EAF00A85D93 /* MockerTests.swift */; };
50D4607320653F4500A85D93 /* Resources in Resources */ = {isa = PBXBuildFile; fileRef = 50D4607220653F4500A85D93 /* Resources */; };
8ED91F36283AFDC300EA8E99 /* wetransfer_bot_avatar.png in Resources */ = {isa = PBXBuildFile; fileRef = 8ED91F32283AFDC300EA8E99 /* wetransfer_bot_avatar.png */; };
8ED91F37283AFDC300EA8E99 /* example.json in Resources */ = {isa = PBXBuildFile; fileRef = 8ED91F34283AFDC300EA8E99 /* example.json */; };
8ED91F38283AFDC300EA8E99 /* sample-redirect-get.data in Resources */ = {isa = PBXBuildFile; fileRef = 8ED91F35283AFDC300EA8E99 /* sample-redirect-get.data */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -43,7 +45,9 @@
50D4606320653EAF00A85D93 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
50D4606B20653F1F00A85D93 /* Mocker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Mocker.h; sourceTree = "<group>"; };
50D4606D20653F1F00A85D93 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
50D4607220653F4500A85D93 /* Resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Resources; sourceTree = "<group>"; };
8ED91F32283AFDC300EA8E99 /* wetransfer_bot_avatar.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wetransfer_bot_avatar.png; sourceTree = "<group>"; };
8ED91F34283AFDC300EA8E99 /* example.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = example.json; sourceTree = "<group>"; };
8ED91F35283AFDC300EA8E99 /* sample-redirect-get.data */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "sample-redirect-get.data"; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -101,9 +105,9 @@
50D4605A20653EAF00A85D93 /* MockerTests */ = {
isa = PBXGroup;
children = (
8ED91F31283AFDC300EA8E99 /* Resources */,
50D4605B20653EAF00A85D93 /* MockedData.swift */,
50D4605C20653EAF00A85D93 /* MockerTests.swift */,
50D4607220653F4500A85D93 /* Resources */,
50D4606220653EAF00A85D93 /* Supporting Files */,
);
path = MockerTests;
Expand Down Expand Up @@ -134,6 +138,24 @@
path = "Supporting Files";
sourceTree = "<group>";
};
8ED91F31283AFDC300EA8E99 /* Resources */ = {
isa = PBXGroup;
children = (
8ED91F32283AFDC300EA8E99 /* wetransfer_bot_avatar.png */,
8ED91F33283AFDC300EA8E99 /* JSON Files */,
8ED91F35283AFDC300EA8E99 /* sample-redirect-get.data */,
);
path = Resources;
sourceTree = "<group>";
};
8ED91F33283AFDC300EA8E99 /* JSON Files */ = {
isa = PBXGroup;
children = (
8ED91F34283AFDC300EA8E99 /* example.json */,
);
path = "JSON Files";
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXHeadersBuildPhase section */
Expand All @@ -152,9 +174,9 @@
isa = PBXNativeTarget;
buildConfigurationList = 501E26A81F3DAE370048F39E /* Build configuration list for PBXNativeTarget "Mocker" */;
buildPhases = (
501E26911F3DAE370048F39E /* Headers */,
501E268F1F3DAE370048F39E /* Sources */,
501E26901F3DAE370048F39E /* Frameworks */,
501E26911F3DAE370048F39E /* Headers */,
501E26921F3DAE370048F39E /* Resources */,
503446441F3DE70C0039D5E4 /* SwiftLint */,
);
Expand Down Expand Up @@ -240,7 +262,9 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
50D4607320653F4500A85D93 /* Resources in Resources */,
8ED91F36283AFDC300EA8E99 /* wetransfer_bot_avatar.png in Resources */,
8ED91F38283AFDC300EA8E99 /* sample-redirect-get.data in Resources */,
8ED91F37283AFDC300EA8E99 /* example.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
13 changes: 9 additions & 4 deletions MockerTests/MockedData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@
//

import Foundation
import UIKit

/// Contains all available Mocked data.
public final class MockedData {
public static let botAvatarImageFileUrl: URL = Bundle(for: MockedData.self).url(forResource: "wetransfer_bot_avatar", withExtension: "png")!
public static let exampleJSON: URL = Bundle(for: MockedData.self).url(forResource: "Resources/JSON Files/example", withExtension: "json")!
public static let redirectGET: URL = Bundle(for: MockedData.self).url(forResource: "Resources/sample-redirect-get", withExtension: "data")!
public static let botAvatarImageFileUrl: URL = Bundle.module.url(forResource: "wetransfer_bot_avatar", withExtension: "png")!
public static let exampleJSON: URL = Bundle.module.url(forResource: "example", withExtension: "json")!
public static let redirectGET: URL = Bundle.module.url(forResource: "sample-redirect-get", withExtension: "data")!
}

extension Bundle {
#if !SWIFT_PACKAGE
static let module = Bundle(for: MockedData.self)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, how does this work for the constants above if we're SWIFT_PACKAGE is YES? From what I can tell, it shouldn't compile? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SPM generates this special Bundle.module accessor (https://developer.apple.com/documentation/swift_packages/bundling_resources_with_a_swift_package) Unfortunately, it doesn't exist in non-SPM targets, so here I was trying to keep both Xcode iOS tests and SPM tests working with resources.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this makes sense to me! 🙂

#endif
}

internal extension URL {
Expand Down
91 changes: 37 additions & 54 deletions MockerTests/MockerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
//

import XCTest
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
@testable import Mocker

final class MockerTests: XCTestCase {
Expand Down Expand Up @@ -36,17 +39,15 @@ final class MockerTests: XCTestCase {
let expectation = self.expectation(description: "Data request should succeed")
let originalURL = URL(string: "https://avatars3.githubusercontent.com/u/26250426?v=4&s=400")!

let mockedData = MockedData.botAvatarImageFileUrl.data
let mock = Mock(url: originalURL, dataType: .imagePNG, statusCode: 200, data: [
.get: MockedData.botAvatarImageFileUrl.data
.get: mockedData
])

mock.register()
URLSession.shared.dataTask(with: originalURL) { (data, _, error) in
XCTAssertNil(error)
let image: UIImage = UIImage(data: data!)!
let sampleImage: UIImage = UIImage(contentsOfFile: MockedData.botAvatarImageFileUrl.path)!

XCTAssertEqual(image.size, sampleImage.size, "Image should be returned mocked")
XCTAssertEqual(data, mockedData, "Image should be returned mocked")
expectation.fulfill()
}.resume()

Expand All @@ -58,16 +59,14 @@ final class MockerTests: XCTestCase {
let expectation = self.expectation(description: "Data request should succeed")
let originalURL = URL(string: "https://www.wetransfer.com/sample-image.png")

let mockedData = MockedData.botAvatarImageFileUrl.data
Mock(fileExtensions: "png", dataType: .imagePNG, statusCode: 200, data: [
.get: MockedData.botAvatarImageFileUrl.data
.get: mockedData
]).register()

URLSession.shared.dataTask(with: originalURL!) { (data, _, error) in
XCTAssertNil(error)
let image: UIImage = UIImage(data: data!)!
let sampleImage: UIImage = UIImage(contentsOfFile: MockedData.botAvatarImageFileUrl.path)!

XCTAssertEqual(image.size, sampleImage.size, "Image should be returned mocked")
XCTAssertEqual(data, mockedData, "Image should be returned mocked")
expectation.fulfill()
}.resume()

Expand All @@ -82,23 +81,15 @@ final class MockerTests: XCTestCase {
Mock(fileExtensions: "png", dataType: .imagePNG, statusCode: 400, data: [
.get: Data()
]).register()

let mockedData = MockedData.botAvatarImageFileUrl.data
Mock(url: originalURL, ignoreQuery: true, dataType: .imagePNG, statusCode: 200, data: [
.get: MockedData.botAvatarImageFileUrl.data
.get: mockedData
]).register()

URLSession.shared.dataTask(with: originalURL) { (data, _, error) in
XCTAssertNil(error)
guard let data = data else {
XCTFail("Data is nil")
return
}
guard let image: UIImage = UIImage(data: data) else {
XCTFail("Invalid data \(String(describing: data))")
return
}
let sampleImage: UIImage = UIImage(contentsOfFile: MockedData.botAvatarImageFileUrl.path)!

XCTAssertEqual(image.size, sampleImage.size, "Image should be returned mocked")
XCTAssertEqual(data, mockedData, "Image should be returned mocked")
expectation.fulfill()
}.resume()

Expand All @@ -110,27 +101,17 @@ final class MockerTests: XCTestCase {
let expectation = self.expectation(description: "Data request should succeed")
let originalURL = URL(string: "https://www.wetransfer.com/sample-image.png?width=200&height=200")!

let mockedData = MockedData.botAvatarImageFileUrl.data
Mock(url: originalURL, ignoreQuery: true, dataType: .imagePNG, statusCode: 200, data: [
.get: MockedData.botAvatarImageFileUrl.data
.get: mockedData
]).register()

/// Make it different compared to the mocked URL.
let customURL = URL(string: originalURL.absoluteString + "&" + UUID().uuidString)!

URLSession.shared.dataTask(with: customURL) { (data, _, error) in
XCTAssertNil(error)
guard let data = data else {
XCTFail("Data is nil")
return
}

guard let image: UIImage = UIImage(data: data) else {
XCTFail("Invalid data \(String(describing: data))")
return
}
let sampleImage: UIImage = UIImage(contentsOfFile: MockedData.botAvatarImageFileUrl.path)!

XCTAssertEqual(image.size, sampleImage.size, "Image should be returned mocked")
XCTAssertEqual(data, mockedData, "Image should be returned mocked")
expectation.fulfill()
}.resume()

Expand Down Expand Up @@ -173,13 +154,13 @@ final class MockerTests: XCTestCase {
/// It should return the additional headers.
func testAdditionalHeaders() {
let expectation = self.expectation(description: "Data request should succeed")
let headers = ["testkey": "testvalue"]
let headers = ["Testkey": "testvalue"]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added capitalisation to HTTP headers because Foundation on Linux does this 🤷

let mock = Mock(dataType: .json, statusCode: 200, data: [.get: Data()], additionalHeaders: headers)
mock.register()

URLSession.shared.dataTask(with: mock.request) { (_, response, error) in
XCTAssertNil(error)
XCTAssertEqual(((response as? HTTPURLResponse)?.allHeaderFields["testkey"] as? String), "testvalue", "Additional headers should be added.")
XCTAssertEqual(((response as? HTTPURLResponse)?.allHeaderFields["Testkey"] as? String), "testvalue", "Additional headers should be added.")
expectation.fulfill()
}.resume()

Expand All @@ -192,12 +173,12 @@ final class MockerTests: XCTestCase {
let mock = Mock(dataType: .json, statusCode: 200, data: [.get: Data()], additionalHeaders: ["testkey": "testvalue"])
mock.register()

let newMock = Mock(dataType: .json, statusCode: 200, data: [.get: Data()], additionalHeaders: ["newkey": "newvalue"])
let newMock = Mock(dataType: .json, statusCode: 200, data: [.get: Data()], additionalHeaders: ["Newkey": "newvalue"])
newMock.register()

URLSession.shared.dataTask(with: mock.request) { (_, response, error) in
XCTAssertNil(error)
XCTAssertEqual(((response as? HTTPURLResponse)?.allHeaderFields["newkey"] as? String), "newvalue", "Additional headers should be added.")
XCTAssertEqual(((response as? HTTPURLResponse)?.allHeaderFields["Newkey"] as? String), "newvalue", "Additional headers should be added.")
expectation.fulfill()
}.resume()

Expand All @@ -209,8 +190,9 @@ final class MockerTests: XCTestCase {
let expectation = self.expectation(description: "Data request should succeed")
let originalURL = URL(string: "https://www.wetransfer.com/sample-image.png")

let mockedData = MockedData.botAvatarImageFileUrl.data
Mock(fileExtensions: "png", dataType: .imagePNG, statusCode: 200, data: [
.get: MockedData.botAvatarImageFileUrl.data
.get: mockedData
]).register()

let configuration = URLSessionConfiguration.default
Expand All @@ -219,18 +201,7 @@ final class MockerTests: XCTestCase {

urlSession.dataTask(with: originalURL!) { (data, _, error) in
XCTAssertNil(error)
guard let data = data else {
XCTFail("Data is nil")
return
}

guard let image: UIImage = UIImage(data: data) else {
XCTFail("Invalid data \(String(describing: data))")
return
}
let sampleImage: UIImage = UIImage(contentsOfFile: MockedData.botAvatarImageFileUrl.path)!

XCTAssertEqual(image.size, sampleImage.size, "Image should be returned mocked")
XCTAssertEqual(data, mockedData, "Image should be returned mocked")
expectation.fulfill()
}.resume()

Expand Down Expand Up @@ -258,7 +229,10 @@ final class MockerTests: XCTestCase {
}

/// It should correctly handle redirect responses.
func testRedirectResponse() {
func testRedirectResponse() throws {
#if os(Linux)
throw XCTSkip("The URLSession swift-corelibs-foundation implementation doesn't currently handle redirects directly")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌. Perhaps we can even leave a reference to the issue that tracks this here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is the code that throws this error https://github.com/apple/swift-corelibs-foundation/blob/main/Sources/FoundationNetworking/URLSession/URLSessionTask.swift#L1037 I haven't found any open issue, seems like URLSession hasn't been fully implemented on Linux, also, not sure what's their plans in swift-corelibs-foundation

#endif
let expectation = self.expectation(description: "Data request should be cancelled")
let urlWhichRedirects: URL = URL(string: "https://we.tl/redirect")!
Mock(url: urlWhichRedirects, dataType: .html, statusCode: 200, data: [.get: MockedData.redirectGET.data]).register()
Expand Down Expand Up @@ -447,10 +421,14 @@ final class MockerTests: XCTestCase {
XCTAssertNil(urlresponse)
XCTAssertNotNil(error)
if let error = error {
#if os(Linux)
XCTAssertEqual(error as? TestExampleError, .example)
#else
// there's not a particularly elegant way to verify an instance
// of an error, but this is a convenient workaround for testing
// purposes
XCTAssertTrue(String(describing: error).contains("TestExampleError"))
#endif
}

expectation.fulfill()
Expand All @@ -460,7 +438,10 @@ final class MockerTests: XCTestCase {
}

/// It should cache response
func testMockCachePolicy() {
func testMockCachePolicy() throws {
#if os(Linux)
throw XCTSkip("URLSessionTask in swift-corelibs-foundation doesn't cache response for custom protocols")
#endif
let expectation = self.expectation(description: "Data request should succeed")
let originalURL = URL(string: "https://www.wetransfer.com/example.json")!

Expand All @@ -471,7 +452,9 @@ final class MockerTests: XCTestCase {
).register()

let configuration = URLSessionConfiguration.default
#if !os(Linux)
configuration.urlCache = URLCache()
#endif
configuration.protocolClasses = [MockingURLProtocol.self]
let urlSession = URLSession(configuration: configuration)

Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ let package = Package(name: "Mocker",
// dev .product(name: "WeTransferPRLinter", package: "WeTransferPRLinter")
// dev ], path: "Submodules/WeTransfer-iOS-CI/DangerFakeSources", sources: ["DangerFakeSource.swift"]),
.target(name: "Mocker", path: "Sources"),
.testTarget(name: "MockerTests", dependencies: ["Mocker"], path: "MockerTests")
.testTarget(name: "MockerTests", dependencies: ["Mocker"], path: "MockerTests", resources: [.process("Resources")])
],
swiftLanguageVersions: [.v5])