I've got a conceptual Animal API client class that will interface with a Rest Api below (it may have syntax errors, I'm typing it from my head).
class AnimalApi {
let connectionInfo: ApiConnectionInfo
init(connectionInfo: ApiConnectionInfo) {
self.connectionInfo = connectionInfo
}
func login(username: String, password: String) {
// login stuff
}
func logout() {
// logout stuff
}
func get(url: String) {
}
func post(url: String) {
}
func delete(url: String) {
}
}
// Dogs
extension AnimalApi {
func getAllDogs() -> Dogs {
return get("dogResourceUrl")
}
func deleteDog() { }
func updateDog() { }
}
// Cats
extension AnimalApi {
func getAllCats() { }
func deleteCat() { }
func updateCat() { }
}
Is there a better way to group code in Swift instead of using extensions? There will be dozens of API resources I have to call that are all located on the same API server. I am trying to avoid the following...
let api = AnimalApi()
let dogs = api. // bombarded with all functions here, ideally something like api.Dogs.getAll would be more manageable
I realize that Apple uses extensions to group their code in their Swift API, but is there a better way? Sub classes maybe?
EDIT: I'd like to avoid sub classes if possible. This is because I am planning on having a single global instance of the AnimalApi since it will be accessed throughout the app constantly. Maybe make AnimalAPi members static and have separate classes with static members that contain functions that call the static AnimalApi.
class DogApi {
class func all() { return AnimalApi.get("dogResourceUri") }
}
let dogs = DogApi.all()
The below sample code is an attempt to accomplish your requirement.
Hope it helps.
typealias Task = () -> ()
typealias Api = (getAll: Task, delete: Task, update: Task)
class AnimalApi {
let dogs: Api = {
func getAll() { }
func delete() { }
func update() { }
return (getAll, delete, update)
}()
let cats: Api = {
func getAll() { }
func delete() { }
func update() { }
return (getAll, delete, update)
}()
}
Now you can use it anywhere in the app as follows without getting bombarded with all different functions:
let api = AnimalApi()
let dogs = api.dogs.getAll
Related
As I understand, it is best to only test public methods of a class.
Let's have a look at this example. I have a view model for the view controller.
protocol MyViewModelProtocol {
var items: [SomeItem] { get }
var onInsertItemsAtIndexPaths: (([IndexPath]) -> Void)? { get set }
func viewLoaded()
}
class MyViewModel: MyViewModelProtocol {
func viewLoaded() {
let items = createDetailsCellModels()
updateCellModels(with: items)
requestDetails()
}
}
I want to test class viewLoaded(). This class calls two other methods - updateItems() and requestDetails()
One of the methods sets up the items and the other one call API to retrieve data and update those items. Items array us updated two times and onInsertItemsAtIndexPaths are called two times - when setting up those items and when updating with new data.
I can test whether after calling viewLoaded() expected items are set up and that onInsertItemsAtIndexPaths is called.
However, the test method will become rather complex.
What is your view, should I test those two methods separately or just write this one huge test?
By testing only viewLoaded(), my idea is that the implementation can change and I only care that results are what I expect.
I think the same thing, only public functions should be tested, since public ones use private ones, and your view on MVVM is correct. You can improve it by adding a DataSource and a Mapper that allows you to improve testing.
However, yes, the test seems huge to me, the tests should test simple units and ensure that small parts of the code work well, with the example you show is difficult, you need to divide by layers (clean code).
In the example you load the data into the viewModel and make it difficult to mockup the data. But if you have a Domain layer you can pass the UseCase mock to the viewModel and control the result. If you run a test on your example, the result will also depend on what the endpoint returns. (404, 200, empty array, data with error ...). So it is important, for testing purposes, to have a good separation by layers. (Presentation, Domain and Data) to be able to test each one separately.
I give you an example of how I would test a view mode, sure there are better and cooler examples, but it's an approach.
Here you can see a viewModel
protocol BeersListViewModel: BeersListViewModelInput, BeersListViewModelOutput {}
protocol BeersListViewModelInput {
func viewDidLoad()
func updateView()
func image(url: String?, index: Int) -> Cancellable?
}
protocol BeersListViewModelOutput {
var items: Box<BeersListModel?> { get }
var loadingStatus: Box<LoadingStatus?> { get }
var error: Box<Error?> { get }
}
final class DefaultBeersListViewModel {
private let beersListUseCase: BeersListUseCase
private var beersLoadTask: Cancellable? { willSet { beersLoadTask?.cancel() }}
var items: Box<BeersListModel?> = Box(nil)
var loadingStatus: Box<LoadingStatus?> = Box(.stop)
var error: Box<Error?> = Box(nil)
#discardableResult
init(beersListUseCase: BeersListUseCase) {
self.beersListUseCase = beersListUseCase
}
func viewDidLoad() {
updateView()
}
}
// MARK: Update View
extension DefaultBeersListViewModel: BeersListViewModel {
func updateView() {
self.loadingStatus.value = .start
beersLoadTask = beersListUseCase.execute(completion: { (result) in
switch result {
case .success(let beers):
let beers = beers.map { DefaultBeerModel(beer: $0) }
self.items.value = DefaultBeersListModel(beers: beers)
case .failure(let error):
self.error.value = error
}
self.loadingStatus.value = .stop
})
}
}
// MARK: - Images
extension DefaultBeersListViewModel {
func image(url: String?, index: Int) -> Cancellable? {
guard let url = url else { return nil }
return beersListUseCase.image(with: url, completion: { (result) in
switch result {
case .success(let imageData):
self.items.value?.items?[index].image.value = imageData
case .failure(let error ):
print("image error: \(error)")
}
})
}
}
Here you can see the viewModel test using mocks for the data and view.
class BeerListViewModelTest: XCTestCase {
private enum ErrorMock: Error {
case error
}
class BeersListUseCaseMock: BeersListUseCase {
var error: Error?
var expt: XCTestExpectation?
func execute(completion: #escaping (Result<[BeerEntity], Error>) -> Void) -> Cancellable? {
let beersMock = BeersMock.makeBeerListEntityMock()
if let error = error {
completion(.failure(error))
} else {
completion(.success(beersMock))
}
expt?.fulfill()
return nil
}
func image(with imageUrl: String, completion: #escaping (Result<Data, Error>) -> Void) -> Cancellable? {
return nil
}
}
func testWhenAPIReturnAllData() {
let beersListUseCaseMock = BeersListUseCaseMock()
beersListUseCaseMock.expt = self.expectation(description: "All OK")
beersListUseCaseMock.error = nil
let viewModel = DefaultBeersListViewModel(beersListUseCase: beersListUseCaseMock)
viewModel.items.bind { (_) in}
viewModel.updateView()
waitForExpectations(timeout: 10, handler: nil)
XCTAssertNotNil(viewModel.items.value)
XCTAssertNil(viewModel.error.value)
XCTAssert(viewModel.loadingStatus.value == .stop)
}
func testWhenDataReturnsError() {
let beersListUseCaseMock = BeersListUseCaseMock()
beersListUseCaseMock.expt = self.expectation(description: "Error")
beersListUseCaseMock.error = ErrorMock.error
let viewModel = DefaultBeersListViewModel(beersListUseCase: beersListUseCaseMock)
viewModel.updateView()
waitForExpectations(timeout: 10, handler: nil)
XCTAssertNil(viewModel.items.value)
XCTAssertNotNil(viewModel.error.value)
XCTAssert(viewModel.loadingStatus.value == .stop)
}
}
in this way you can test the view, the business logic and the data separately, in addition to being a code that is very reusable.
Hope this helps you, I have it posted on github in case you need it.
https://github.com/cardona/MVVM
I'm trying to build an API pattern.
I have several different APIs that have the same functions for some, and some have additional functions that other APIs do not have and should not have access.
The problem with the protocol is that they all have access. How to limit access if the function is not override?
apiRequest:
class APIRequest {
static func loginCli(completion: (UserClinic) -> ()) {
}
static func loginFami(completion: (User) -> ()) {
}
static func loginDavid(completion: (UserDavid) -> ()) {
}
}
Protol and API:
protocol API {
func login<T>(completion: (T) -> ())
func saveClient()
}
extension API {
func saveClient() {
saveClient()
}
}
class FirstAPI: API {
func login<T>(completion: (T) -> ()) {
APIRequest.loginFami { (user) in
}
}
}
class SecondAPI: API {
func login<T>(completion: (T) -> ()) {
APIRequest.loginCli { (user) in
}
}
}
class ThreeAPI: API {
func login<T>(completion: (T) -> ()) {
APIRequest.loginDavid { (user) in
}
}
func saveClient() {
// Save client
}
}
View model:
class LoginViewModel {
var apiClient: API
init() {
// Below its good
apiClient = ThreeAPI()
apiClient.saveClient()
// Below its not good
apiClient = FirstAPI()
apiClient.saveClient() // I want this is not accessible
// Below its good
apiClient = SecondAPI()
apiClient.saveClient() // I want this is not accessible
}
}
In my case I need only the third API to have access to the function saveClient()
If you don't want the saveClient method on FirstAPI and SecondAPI, you need to extract that method to a separate protocol. Something like:
protocol APIThatCanLogin {
func login<T>(completion: (T) -> ())
}
protocol APIThatCanSave {
func saveClient()
}
class FirstAPI: APIThatCanLogin { }
class SecondAPI: APIThatCanLogin { }
class ThreeAPI: APIThatCanLogin, APIThatCanSave { }
But perhaps you should reconsider using protocols. At least in this example it doesn't offer any benefits.
Try this.
protocol API {
func login<T>(completion: (T) -> ())
}
extension API where Self: ThreeAPI {
func saveClient() {
}
}
In the above code, protocol API extension is only available to object of type ThreeAPI.
I've used ThreeAPI directly here. You can make a common type for all the classes that need saveClient() and use that type in place of ThreeAPI
Usage:
apiClient = ThreeAPI()
(apiClient as? ThreeAPI)?.saveClient()
apiClient = FirstAPI() //saveClient() not available
apiClient = SecondAPI() //saveClient() not available
Edit-1:
Create a CommonAPI parent class for all the classes that need saveClient() method.
class CommonAPI {
func saveClient() {
print("CommonAPI saveClient()")
}
}
Inherit ThreeAPI and FourAPI from CommonAPI
class ThreeAPI: CommonAPI, API {
func login<T>(completion: (T) -> ()) {
}
override func saveClient() {
print("ThreeAPI saveClient()")
}
}
class FourAPI: CommonAPI, API {
func login<T>(completion: (T) -> ()) {
}
override func saveClient() {
print("FourAPI saveClient()")
}
}
Simply use a switch statement to call saveClient() on all the objects of type CommonAPI.
apiClient = FourAPI()
apiClient = ThreeAPI()
apiClient = FirstAPI()
apiClient = SecondAPI()
switch apiClient {
case is CommonAPI:
(apiClient as? CommonAPI)?.saveClient()
default:
break
}
In this case, you won't need to parse each and every type.
There is not going to be a solution to this as described. The type API means "has the following methods and properties." You cannot assign something to a variable of type API and then say "but it doesn't have some of the methods." That violates LSP, which is the foundation of these kinds of types. Consider the following code:
func save(api: API) {
api.saveClient()
}
If the feature you're describing could be implemented, should this compile or not? If it did compile, and then it were called with FirstAPI(), what should happen then?
If you want saveClient to be a no-op (to do nothing), then that's very straightforward. Provide an empty default implementation:
extension API {
func saveClient() {}
}
If you want some APIs to be savable, and some not to be savable, then that's straightforward too:
protocol API {
func login<T>(completion: (T) -> ())
}
protocol SavableAPI: API {
func saveClient()
}
But the init method you've written cannot be made to work. It violates the underlying goal of having a type.
I've checked few topics related with the same issue but I can't find any solution to this problem inside my own code...
The error occurs when I try to override the function tune() in my Piano: Instrument subclass.
I've copy/past the superclass original function to ensure that the syntax is strictly the same.
Also, it seems to me that the subclass is well done, because the super.init method seems to work as intended (no compiler error).
Please, can someone point out to me where is my error ?
The code :
class Music {
let notes: [String]
init(notes: [String]) {
self.notes = notes
}
func prepared() -> String {
return notes.joined(separator: " ")
}
}
class Instrument {
let model: String
init(model: String) {
self.model = model
func tune() -> String {
fatalError("Implement this method for \(model)")
}
func play(_ music: Music) -> String {
return music.prepared()
}
func perform(_ music: Music) {
print(tune())
print(play(music))
}
}
}
class Piano: Instrument {
let hasPedals: Bool
init(hasPedals: Bool, model: String) {
self.hasPedals = hasPedals
super.init(model: model)
}
override func tune() -> String {
fatalError("Implement this method for \(model)")
}
}
class Guitar: Instrument {
let hasAmplifyer: Bool
init(hasAmplifyer: Bool, model: String) {
self.hasAmplifyer = hasAmplifyer
super.init(model: model)
}
}
Thank you very much !
You have accidentally defined your tune, play, and perform functions inside of your init function. Move them to the top level:
class Instrument {
let model: String
init(model: String) {
self.model = model
}
func tune() -> String {
fatalError("Implement this method for \(model)")
}
func play(_ music: Music) -> String {
return music.prepared()
}
func perform(_ music: Music) {
print(tune())
print(play(music))
}
}
Swift sees no tune function to override because it expects it to be at the top level in Instrument.
I have multiple protocols that have the same function name. Some protocols have associated types, where I can't figure out how to call the functions as I do in non-generic protocols. I get the error: Protocol 'MyProtocol1' can only be used as a generic contraint because it has Self or associated type requirements
Here's what I'm trying to do:
protocol Serviceable {
associatedtype DataType
func get(handler: ([DataType] -> Void)?)
}
struct PostService: Serviceable {
func get(handler: ([String] -> Void)? = nil) {
print("Do something...")
}
}
protocol MyProtocol1: class {
associatedtype ServiceType: Serviceable
var service: ServiceType { get }
}
extension MyProtocol1 {
func didLoad(delegate: Self) {
print("MyProtocol1.didLoad()")
}
}
protocol MyProtocol2: class {
}
extension MyProtocol2 {
func didLoad(delegate: MyProtocol2) {
print("MyProtocol2.didLoad()")
}
}
class MyViewController: UIViewController, MyProtocol1, MyProtocol2 {
let service = PostService()
override func viewDidLoad() {
super.viewDidLoad()
didLoad(self as MyProtocol1) // Error here: Protocol 'MyProtocol1' can only be used as a generic contraint because it has Self or associated type requirements
didLoad(self as MyProtocol2)
}
}
How can I specifically call the function from a generic protocol extension?
It's simple to achieve by turning the protocol into a generic (see below), or by creating a type eraser for these protocols, but this very strongly suggests that you have a design problem and you should redesign your classes and/or extensions. A collision like this suggests strongly that MyStruct is doing too many things itself because it's being pulled in multiple directions by MyProtocol1 and MyProtocol2. There should likely be two objects here instead. (Composition rather than inheritance.)
class MyStruct: MyProtocol1, MyProtocol2 {
let service = PostService()
func prot1Load<T: MyProtocol1>(t: T) {
t.didLoad()
}
func prot2Load<T: MyProtocol2>(t: T) {
t.didLoad()
}
init() {
prot1Load(self)
prot2Load(self)
}
}
To your particular example in the comments, I would use composition rather than inheritance. You're treating protocols like multiple-inheritance, which is almost never right. Instead compose out of things that conform to a protocol.
protocol LoadProviding {
func load()
}
struct MyLoader1: LoadProviding {
func load() {
print("MyLoader1.didLoad()")
}
}
struct MyLoader2: LoadProviding {
func load() {
print("MyLoader2.didLoad()")
}
}
protocol Loader {
var loaders: [LoadProviding] { get }
}
extension Loader {
func loadAll() {
for loader in loaders {
loader.load()
}
}
}
class MyStruct: Loader {
let service = PostService()
let loaders: [LoadProviding] = [MyLoader1(), MyLoader2()]
init() {
loadAll()
}
}
Of course you don't really have to have LoadProviding be a full struct. It could just be a function if that's all you need:
typealias LoadProviding = () -> Void
func myLoader1() {
print("MyLoader1.didLoad()")
}
func myLoader2() {
print("MyLoader2.didLoad()")
}
protocol Loader {
var loaders: [LoadProviding] { get }
}
extension Loader {
func loadAll() {
for loader in loaders {
loader()
}
}
}
class MyStruct: Loader {
let service = PostService()
let loaders: [LoadProviding] = [myLoader1, myLoader2]
init() {
loadAll()
}
}
If you have time to wade through a video on the subject, you may be interested in the Beyond Crusty: Real World Protocols talk from dotSwift. It's about this and similar problems.
I'm currently using Quick + Nimble for my unit testing in Swift. I'm building an Inviter class that sends app invites via different methods.
I need to mock out UIApplication to verify that my code calls openURL.
My code so far:
import Quick
import Nimble
import OCMock
extension Inviter {
convenience init(usingMockApplication mockApplication: UIApplication) {
self.init()
application = mockApplication
}
}
class MockUIApplication : UIApplication {
var application = UIApplication.sharedApplication()
var openedURL: String?
override func openURL(url: NSURL) -> Bool {
openedURL = url.absoluteString
return true
}
}
class InviterSpec: QuickSpec {
override func spec() {
describe("Inviter") {
var mockApplication = MockUIApplication()
var inviter = Inviter(usingMockApplication: mockApplication)
beforeEach() {
inviter = Inviter(usingMockApplication: mockApplication)
}
context("for WhatsApp invites") {
beforeEach() {
inviter.inviteViaWhatsAppWithMessage("Invite Message.")
}
it("should tell the application to open WhatsApp") {
expect(mockApplication.openedURL).toNot(beNil())
}
it("should send WhatsApp the right message") {
let message = mockApplication.openedURL?.lastPathComponent
expect(message).to(equal("Invite%Message."))
}
}
}
}
}
In this approach, my app errors at runtime stating there can understandably be only one UIApplication. Previously, one could make MockUIApplication inherit from NSObject, and pass that in. Unfortunately Swift's strict type checking seems to prevent that too.
Would love any ideas.
You are close. Use a protocol for the functions you need.
protocol UIApplicationProtocol {
func openURL(url: NSURL) -> Bool
}
extension UIApplication: UIApplicationProtocol {}
Then you just need to use the protocol instead of the class
extension Inviter {
convenience init(usingMockApplication mockApplication: UIApplicationProtocol) {
self.init()
application = mockApplication
}
}
You will need to modify the Inviter class to use UIApplicationProtocol as well.