swift-identified-storage

main

m-housh/swift-identified-storage

swift-identified-storage

CI

A swift package for mocking remote storage with a CRUD like interface.

Motivation

It is often required to mock database clients for purposes of Xcode previews or testing code without using a live client. This package is built on top of the IdentifiedArray type from swift-identified-collections and relies on swift-dependencies for controllable clock operations.

Installation

Install this as a swift package into your project.

import PackageDescription

let package = Package(
    ...
    dependencies: [
      .package(url: "https://github.com/m-housh/swift-identified-storage.git", from: "0.1.0")
    ],
    targets: [
      .target(
        name: "<My Target>",
        dependencies: [
          .product(name: "IdentifiedStorage", package: "swift-identified-storage")
        ]
      )
    ]
)

Basic Usage

Given the following Todo model.

struct Todo: Equatable, Identifiable {
  var id: UUID
  var description: String
  var isComplete: Bool = false
}

#if DEBUG
extension Todo {
  static let mocks: [Self] = [
    .init(id: UUID(0), description: "Buy milk"),
    .init(id: UUID(1), description: "Walk the dog"),
    .init(id: UUID(2), description: "Wash the car", isComplete: true)
  ]
}
#endif

And the todo client interface.

struct TodoClient {

  var delete: (Todo.ID) async throws -> Void
  var fetch: (FetchRequest) async throws -> IdentifiedArrayOf<Todo>
  var insert: (InsertRequest) async throws -> Todo
  var update: (Todo.ID, UpdateRequest) async throws -> Todo

  func fetch() async throws -> IdentifiedArrayOf<Todo> {
    try await self.fetch(.all)
  }

  enum FetchRequest {
    case all
    case filtered(by: Filter)

    enum Filter {
      case complete
      case incomplete
    }
  }

  struct InsertRequest {
    let description: String
  }

  struct UpdateRequest {
    let description: String
  }
}

Conform the request types to the appropriate conversion types.

#if DEBUG
import IdentifiedStorage

extension TodoClient.FetchRequest: FetchRequestConvertible {

  func fetch(from values: IdentifiedArrayOf<Todo>) -> IdentifiedArrayOf<Todo> {
    switch self {
    case .all:
      return values
    case let .filtered(by: filter):
      return values.filter {
        $0.isComplete == (filter == .complete ? true : false)
      }
    }
  }
}

extension TodoClient.InsertRequest: InsertRequestConvertible {

  typealias ID = Todo.ID
  
  func transform() -> Todo {
    @Dependency(\.uuid) var uuid;
    return .init(id: uuid(), description: description)
  }
}

extension TodoClient.UpdateRequest: UpdateRequestConvertible {

  typealias ID = Todo.ID
  
  func apply(to state: inout Todo) {
    state.description = description
  }
}
#endif

Create a mock client factory.

extension TodoClient {
  static func mock(
    initialValues todos: [Todo],
    timeDelays: IdentifiedStorageDelays? = .default
  ) -> Self {

    // using the `IdentifiedStorage` as the storage for the mock client.
    // this uses the passed in time delays to simulate a remote data store for
    // use in previews and tests.
    let storage = IdentifiedStorageOf<Todo>(
      initialValues: todos,
      timeDelays: timeDelays
    )

    return TodoClient(
      delete: { try await storage.delete(id: $0) },
      fetch: { try await storage.fetch(request: $0) },
      insert: { try await storage.insert(request: $0) },
      update: { try await storage.update(id: $0, request: $1) }
    )
  }
}

extension TodoClient: DependencyKey {

    ...

    static var previewValue: Self {
      TodoClient.mock(initialValues: .init(uniqueElements: Todo.mocks))
    }
}

Documentation

View the api documentation here.

License

All modules are released under the MIT license. See LICENSE for details.

Description

  • Swift Tools 5.8.0
View More Packages from this Author

Dependencies

Last updated: Thu Apr 11 2024 12:49:55 GMT-0900 (Hawaii-Aleutian Daylight Time)