版本记录
版本号 | 时间 |
---|---|
V1.0 | 2019.08.12 星期一 |
前言
WireMock是基于HTTP的API的模拟器,下面我们就一起来学习和了解。感兴趣的看下面几篇文章。
1. WireMock详细解析(一) —— Xcode中基于WireMock和UI Tests的本地API调用(一)
源码
1. Swift
首先看下工程组织结构
下面就是源码了
1. StartupUtils.swift
import Foundation
struct StartupUtils {
static func shouldRunLocal() -> Bool {
return CommandLine.arguments.contains("-runlocal")
}
}
2. CharacterListTableViewController.swift
import UIKit
class CharacterListTableViewController: UITableViewController {
var networkClient = StarWarsAPINetworklClient()
var viewModel: CharacterListViewModel!
override func viewDidLoad() {
super.viewDidLoad()
tableView.accessibilityIdentifier = AccessibilityIdentifiers.characterListTable
viewModel = CharacterListViewModel(networkClient: networkClient,
delegate: self)
viewModel.requestCharacterList()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard
let viewController = segue.destination as? CharacterDetailViewController,
let selectedIndexPath = tableView.indexPathForSelectedRow
else {
fatalError("Incorrect destination viewcontroller or selectedIndexPath received")
}
let character = viewModel.character(for: selectedIndexPath)
viewController.character = character
}
}
// MARK: - UITableViewDataSource
extension CharacterListTableViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.numberOfCharacters()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "characterCell", for: indexPath) as? CharacterTableViewCell else {
fatalError("Invalid cell type received")
}
let character = viewModel.character(for: indexPath)
cell.configure(with: character)
return cell
}
}
extension CharacterListTableViewController: CharacterListViewModelDelegate {
func characterListWasUpdated(withCharacters characters: [StarWarsCharacter]) {
tableView.reloadData()
}
}
3. CharacterDetailViewController.swift
import UIKit
class CharacterDetailViewController: UIViewController {
@IBOutlet var nameLabel: UILabel!
@IBOutlet var hairColorLabel: UILabel!
@IBOutlet var eyeColorLabel: UILabel!
@IBOutlet var birthYearLabel: UILabel!
var character: StarWarsCharacter!
override func viewDidLoad() {
super.viewDidLoad()
nameLabel.text = character.name
hairColorLabel.text = character.hairColor
eyeColorLabel.text = character.eyeColor
birthYearLabel.text = character.birthYear
setupAccessibilityIdentifiers()
}
func setupAccessibilityIdentifiers() {
nameLabel.accessibilityIdentifier = AccessibilityIdentifiers.characterDetailNameLabel
hairColorLabel.accessibilityIdentifier = AccessibilityIdentifiers.characterDetailHairColorLabel
eyeColorLabel.accessibilityIdentifier = AccessibilityIdentifiers.characterDetailEyeColorLabel
birthYearLabel.accessibilityIdentifier = AccessibilityIdentifiers.characterDetailBirthYearLabel
}
}
4. CharacterListViewModel.swift
import Foundation
protocol CharacterListViewModelDelegate: AnyObject {
func characterListWasUpdated(withCharacters characters: [StarWarsCharacter])
}
class CharacterListViewModel {
weak var delegate: CharacterListViewModelDelegate?
private var characterList: [StarWarsCharacter] = []
private var networkClient: StarWarsAPINetworklClient
init(networkClient: StarWarsAPINetworklClient,
delegate: CharacterListViewModelDelegate) {
self.networkClient = networkClient
self.delegate = delegate
}
func requestCharacterList() {
networkClient.requestAllCharacters { [weak self] characters in
guard let self = self else {
return
}
self.characterList = characters
DispatchQueue.main.async {
self.delegate?.characterListWasUpdated(withCharacters: characters)
}
}
}
func numberOfCharacters() -> Int {
return characterList.count
}
func character(for indexPath: IndexPath) -> StarWarsCharacter {
return characterList[indexPath.row]
}
}
5. CharacterTableViewCell.swift
import UIKit
class CharacterTableViewCell: UITableViewCell {
@IBOutlet var nameLabel: UILabel!
func configure(with character: StarWarsCharacter) {
nameLabel.text = character.name
nameLabel.accessibilityIdentifier = AccessibilityIdentifiers.characterCellNameLabel
accessibilityLabel = AccessibilityIdentifiers.characterCellIdentifier(forCharacterName: character.name)
}
}
6. StarWarsCharacter.swift
struct StarWarsCharacter: Codable {
let name: String
let hairColor: String
let eyeColor: String
let birthYear: String
}
7. CharacterListResponse.swift
import Foundation
struct CharacterListResponse: Codable {
let count: Int
let results: [StarWarsCharacter]
}
8. StarWarsAPINetworkClient.swift
import Foundation
class StarWarsAPINetworklClient {
let defaultSession = URLSession(configuration: .default)
var dataTask: URLSessionDataTask?
var baseURLString: String {
if StartupUtils.shouldRunLocal() {
return "http://localhost:9999/swapi.co/api/"
} else {
return "https://swapi.co/api/"
}
}
func requestAllCharacters(completion: @escaping ([StarWarsCharacter]) -> Void) {
dataTask?.cancel()
let url = baseURL()
let listURL = url.appendingPathComponent("people")
dataTask = defaultSession.dataTask(with: listURL, completionHandler: {(data, response, error) in
guard
error == nil,
let data = data
else {
completion([])
return
}
do {
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
let characterResponse = try jsonDecoder.decode(CharacterListResponse.self, from: data)
completion(characterResponse.results)
} catch {
completion([])
}
})
dataTask?.resume()
}
func baseURL() -> URL {
guard let url = URL(string: baseURLString) else {
fatalError("Invalid url generation")
}
return url
}
}
9. AccessibilityIdentifiers.swift
enum AccessibilityIdentifiers {
// MARK: - Character List
static let characterListTable = "characterListTable"
static let characterCellPrefix = "characterCell"
static let characterCellNameLabel = "characterCellNameLabel"
// MARK: - Character Detail
static let characterDetailNameLabel = "characterDetailNameLabel"
static let characterDetailHairColorLabel = "characterDetailHairColorLabel"
static let characterDetailEyeColorLabel = "characterDetailEyeColorLabel"
static let characterDetailBirthYearLabel = "characterDetailBirthYearLabel"
static func characterCellIdentifier(forCharacterName name: String) -> String {
return "\(AccessibilityIdentifiers.characterCellPrefix) \(name)"
}
}
10. AccessibilityLabels.swift
import Foundation
enum AccessibilityLabels {
static let characterDetailBackButton = "Characters"
}
11. StarWarsInfoUITests.swift
import XCTest
class StarWarsInfoUITests: XCTestCase {
func testCharacterList() {
let app = XCUIApplication()
app.launchArguments = LaunchArguments.launchLocalArguments
app.launch()
let table = app.tables[AccessibilityIdentifiers.characterListTable]
waitForElementToAppear(table, file: #file, line: #line, elementName: "Character List Table", timeout: 5.0)
let identifiers = generateIdentifierList()
identifiers.forEach { identifier in
let cell = table.cells[identifier]
XCTAssertTrue(cell.exists, "\(identifier) cell should be present")
}
}
func testCellDetail() {
let app = XCUIApplication()
app.launchArguments = LaunchArguments.launchLocalArguments
app.launch()
let table = app.tables[AccessibilityIdentifiers.characterListTable]
waitForElementToAppear(table, file: #file, line: #line, elementName: "Character List Table")
let identifier = "\(AccessibilityIdentifiers.characterCellPrefix) Luke Skywalker"
let cell = table.cells[identifier]
XCTAssertTrue(cell.exists, "\(identifier) cell should be present")
cell.tap()
let nameLabel = app.staticTexts[AccessibilityIdentifiers.characterDetailNameLabel]
XCTAssertEqual(nameLabel.label, "Luke Skywalker", "Name label should match character")
let hairColorLabel = app.staticTexts[AccessibilityIdentifiers.characterDetailHairColorLabel]
XCTAssertEqual(hairColorLabel.label, "blond", "Hair Color label should match character")
let eyeColorLabel = app.staticTexts[AccessibilityIdentifiers.characterDetailEyeColorLabel]
XCTAssertEqual(eyeColorLabel.label, "blue", "Eye Color label should match character")
let birthYearLabel = app.staticTexts[AccessibilityIdentifiers.characterDetailBirthYearLabel]
XCTAssertEqual(birthYearLabel.label, "19BBY", "Name label should match character")
let backButton = app.buttons[AccessibilityLabels.characterDetailBackButton]
XCTAssertTrue(backButton.exists, "Back button should be present")
backButton.tap()
waitForElementToAppear(table, file: #file, line: #line, elementName: "Character List Table")
}
func generateIdentifierList() -> [String] {
let names = [
"Luke Skywalker",
"C-3PO",
"R2-D2"
]
let identifiers = names.map { name in
return "\(AccessibilityIdentifiers.characterCellPrefix) \(name)"
}
return identifiers
}
}
12. XCTestCase+Helpers.swift
import XCTest
extension XCTestCase {
func waitForElementToAppear(_ element: XCUIElement,
file: StaticString,
line: UInt,
elementName: String,
timeout: TimeInterval = 5.0) {
let predicate = NSPredicate(format: "exists == true")
let existsExpectation = expectation(for: predicate,
evaluatedWith: element,
handler: nil)
let result = XCTWaiter().wait(for: [existsExpectation],
timeout: timeout)
guard result == .completed else {
let failureMessage = "\(elementName) should be present)"
XCTFail(failureMessage, file: file, line: line)
return
}
}
}
13. LaunchArguments.swift
import Foundation
struct LaunchArguments {
static var launchLocalArguments: [String] {
return [
"-runlocal"
]
}
}
后记
本篇主要讲述了Xcode中基于WireMock和UI Tests的本地API调用,感兴趣的给个赞或者关注~~~