Added network call for facts
This commit is contained in:
parent
36a58874e0
commit
2bfe9a90cd
62
.gitignore
vendored
62
.gitignore
vendored
@ -0,0 +1,62 @@
|
||||
# Xcode
|
||||
#
|
||||
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
|
||||
|
||||
## User settings
|
||||
xcuserdata/
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
|
||||
## App packaging
|
||||
*.ipa
|
||||
*.dSYM.zip
|
||||
*.dSYM
|
||||
|
||||
## Playgrounds
|
||||
timeline.xctimeline
|
||||
playground.xcworkspace
|
||||
|
||||
# Swift Package Manager
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
|
||||
# Packages/
|
||||
# Package.pins
|
||||
# Package.resolved
|
||||
# *.xcodeproj
|
||||
#
|
||||
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
|
||||
# hence it is not needed unless you have added a package configuration file to your project
|
||||
# .swiftpm
|
||||
|
||||
.build/
|
||||
|
||||
# CocoaPods
|
||||
#
|
||||
# We recommend against adding the Pods directory to your .gitignore. However
|
||||
# you should judge for yourself, the pros and cons are mentioned at:
|
||||
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
||||
#
|
||||
# Pods/
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from the Xcode workspace
|
||||
# *.xcworkspace
|
||||
|
||||
# Carthage
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
||||
# Carthage/Checkouts
|
||||
|
||||
Carthage/Build/
|
||||
|
||||
# fastlane
|
||||
#
|
||||
# It is recommended to not store the screenshots in the git repo.
|
||||
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
|
||||
# For more information about the recommended setup visit:
|
||||
# https://docs.fastlane.tools/best-practices/source-control/#source-control
|
||||
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots/**/*.png
|
||||
fastlane/test_output
|
||||
@ -0,0 +1,15 @@
|
||||
{
|
||||
"originHash" : "b5a4d1a917ee731eae9d288a993a1e905ecfa9e87cb169c76452216c83b5b4f2",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "mocker",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/WeTransfer/Mocker",
|
||||
"state" : {
|
||||
"revision" : "95fa785c751f6bc40c49e112d433c3acf8417a97",
|
||||
"version" : "3.0.2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
Binary file not shown.
@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>App.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
<dict>
|
||||
<key>C3BB763A2ED0C38800D56534</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>C3BB76472ED0C38900D56534</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>C3BB76512ED0C38900D56534</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
8
Meow/.gitignore
vendored
8
Meow/.gitignore
vendored
@ -1,8 +0,0 @@
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/configuration/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
||||
@ -3,6 +3,14 @@
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let commonSwiftSettings: [SwiftSetting] = [
|
||||
.enableExperimentalFeature("StrictConcurrency"),
|
||||
.enableUpcomingFeature("InferIsolatedConformances"),
|
||||
.enableUpcomingFeature("NonisolatedNonsendingByDefault"),
|
||||
.defaultIsolation(nil),
|
||||
.swiftLanguageMode(.v6)
|
||||
]
|
||||
|
||||
let package = Package(
|
||||
name: "Meow",
|
||||
platforms: [.iOS(.v18), .macOS(.v15)],
|
||||
@ -13,15 +21,26 @@ let package = Package(
|
||||
targets: ["Meow"]
|
||||
),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/WeTransfer/Mocker", from: "3.0.2"),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package, defining a module or a test suite.
|
||||
// Targets can depend on other targets in this package and products from dependencies.
|
||||
.target(
|
||||
name: "Meow"
|
||||
name: "Meow",
|
||||
swiftSettings: commonSwiftSettings
|
||||
),
|
||||
.testTarget(
|
||||
name: "MeowTests",
|
||||
dependencies: ["Meow"]
|
||||
dependencies: [
|
||||
"Meow",
|
||||
"Mocker"
|
||||
],
|
||||
resources: [
|
||||
.process("Fixtures")
|
||||
],
|
||||
swiftSettings: commonSwiftSettings
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
@ -7,30 +7,37 @@ import Foundation
|
||||
|
||||
public struct Meow {
|
||||
let baseURL: URL
|
||||
let session: URLSession
|
||||
let urlSession: URLSession
|
||||
|
||||
public init(baseURL: URL, session: URLSession = .shared) {
|
||||
public init(baseURL: URL, urlSession: URLSession = .shared) {
|
||||
self.baseURL = baseURL
|
||||
self.session = session
|
||||
self.urlSession = urlSession
|
||||
}
|
||||
|
||||
public func getFacts() async throws -> [String] {
|
||||
let request = try generateRequest(path: "/")
|
||||
public func getFacts(count: Int? = nil) async throws -> [String] {
|
||||
let params = count.map { ["count": $0] } ?? [:]
|
||||
let request = try generateRequest(path: "/", params: params)
|
||||
let response: FactsResponse = try await decodeRequest(request: request)
|
||||
return response.data
|
||||
}
|
||||
|
||||
// MARK: - Private functionality
|
||||
|
||||
private func generateRequest(path: String) throws -> URLRequest {
|
||||
private func generateRequest(path: String, params: [String: CustomStringConvertible] = [:]) throws -> URLRequest {
|
||||
guard let url = URL(string: path, relativeTo: baseURL) else {
|
||||
throw MeowError.requestError("Couldn't generate URL from path: \(path), baseURL: \(baseURL)")
|
||||
}
|
||||
return URLRequest(url: url)
|
||||
|
||||
let queryItems = params
|
||||
.compactMap { URLQueryItem(name: $0, value: $1.description) }
|
||||
.sorted { a, b in a.name < b.name }
|
||||
return queryItems.count > 0
|
||||
? URLRequest(url: url.appending(queryItems: queryItems))
|
||||
: URLRequest(url: url)
|
||||
}
|
||||
|
||||
private func decodeRequest<T: Decodable>(request: URLRequest) async throws -> T {
|
||||
let (data, response) = try await session.data(for: request)
|
||||
let (data, response) = try await urlSession.data(for: request)
|
||||
guard let response = response as? HTTPURLResponse else {
|
||||
throw MeowError.connectionError("Couldn't get HTTP response from request \(request)")
|
||||
}
|
||||
|
||||
20
Meow/Tests/MeowTests/Fixtures.swift
Normal file
20
Meow/Tests/MeowTests/Fixtures.swift
Normal file
@ -0,0 +1,20 @@
|
||||
//
|
||||
// Fixtures.swift
|
||||
// MeowTests
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class Fixtures {
|
||||
static let facts: Data = loadFixture("facts")
|
||||
|
||||
private static func loadFixture(_ name: String) -> Data {
|
||||
guard let url = Bundle.module.url(forResource: name, withExtension: "json") else {
|
||||
fatalError("Failed to load fixture: \(name).json")
|
||||
}
|
||||
guard let data = try? Data(contentsOf: url) else {
|
||||
fatalError("Unable to get Data from contents of \(url)")
|
||||
}
|
||||
return data
|
||||
}
|
||||
}
|
||||
9
Meow/Tests/MeowTests/Fixtures/facts.json
Normal file
9
Meow/Tests/MeowTests/Fixtures/facts.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"data": [
|
||||
"Abraham Lincoln loved cats. He had four of them while he lived in the White House.",
|
||||
"Many cats cannot properly digest cows milk. Milk and milk products give them diarrhea.",
|
||||
"Tylenol and chocolate are both poisonous to cats.",
|
||||
"The average cat food meal is the equivalent to about five mice.",
|
||||
"The way you treat kittens in the early stages of it's life will render it's personality traits later in life."
|
||||
]
|
||||
}
|
||||
@ -5,15 +5,45 @@
|
||||
|
||||
import Foundation
|
||||
import Testing
|
||||
import Mocker
|
||||
@testable import Meow
|
||||
|
||||
@Suite("Meow Tests")
|
||||
struct MeowTests {
|
||||
private let baseURL = URL(string: "https://meow.meow")!
|
||||
let urlSession: URLSession = {
|
||||
let configuration = URLSessionConfiguration.default
|
||||
configuration.protocolClasses = [MockingURLProtocol.self]
|
||||
return URLSession(configuration: configuration)
|
||||
}()
|
||||
|
||||
@Test
|
||||
func constructor() async throws {
|
||||
let instance = Meow(baseURL: baseURL)
|
||||
let instance = vendInstance()
|
||||
#expect(instance.baseURL == baseURL)
|
||||
}
|
||||
|
||||
@Test
|
||||
func getFacts() async throws {
|
||||
let instance = vendInstance()
|
||||
let count = 5
|
||||
mockPath("/", queryItems: ["count": count], data: [.get: Fixtures.facts])
|
||||
let facts = try await instance.getFacts(count: count)
|
||||
#expect(facts.count == count)
|
||||
}
|
||||
|
||||
// MARK: - Private functionality
|
||||
|
||||
private func vendInstance() -> Meow {
|
||||
return Meow(baseURL: baseURL, urlSession: urlSession)
|
||||
}
|
||||
|
||||
private func mockPath(_ path: String, headers: [String: String] = [:], queryItems: [String: CustomStringConvertible] = [:], data: [Mock.HTTPMethod: Data] = [:]) {
|
||||
let urlQueryItems = queryItems
|
||||
.compactMap({ URLQueryItem(name: $0, value: $1.description) })
|
||||
.sorted { a, b in a.name < b.name }
|
||||
let base = baseURL.appendingPathComponent(path)
|
||||
let url = urlQueryItems.isEmpty ? base : base.appending(queryItems: urlQueryItems)
|
||||
Mock(url: url, ignoreQuery: false, contentType: .json, statusCode: 200, data: data, additionalHeaders: headers).register()
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user