Created
July 5, 2019 13:48
-
-
Save truizlop/bb859567ea8318dd81650ea736ce9d84 to your computer and use it in GitHub Desktop.
Testing effectful services with BowEffects (not released yet)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Bow | |
import BowEffects | |
typealias UserID = String | |
struct UserProfile: Equatable { | |
let name: String | |
} | |
protocol Database { | |
var database: DatabaseService { get } | |
} | |
protocol DatabaseService { | |
func lookup(id: UserID) -> Task<UserProfile> | |
func update(id: UserID, profile: UserProfile) -> Task<()> | |
} | |
protocol Logger { | |
var logger: LoggerService { get } | |
} | |
protocol LoggerService { | |
func info(_ id: String) -> Task<()> | |
} | |
struct DatabaseTestService: DatabaseService { | |
let ref: Ref<IOPartial<Error>, State> | |
func lookup(id: UserID) -> Task<UserProfile> { | |
return ref.modify { db in | |
db.lookup(id: id) | |
}.flatMap { x in Task.invoke { x! } }^ | |
} | |
func update(id: UserID, profile: UserProfile) -> Task<()> { | |
return ref.update { db in | |
db.update(id: id, profile: profile) | |
}^ | |
} | |
struct State: Equatable { | |
let map: [UserID: UserProfile] | |
let ops: [String] | |
func log(_ op: String) -> State { | |
return State(map: map, ops: [op] + self.ops) | |
} | |
func lookup(id: UserID) -> (State, UserProfile?) { | |
return (log("Lookup(\(id))"), map[id]) | |
} | |
func update(id: UserID, profile: UserProfile) -> State { | |
var newMap = map | |
newMap[id] = profile | |
return State(map: newMap, ops: ops).log("Update(\(id), \(profile))") | |
} | |
} | |
} | |
struct LoggerTestService: LoggerService { | |
let ref: Ref<IOPartial<Error>, [String]> | |
func info(_ line: String) -> Task<()> { | |
return ref.update { logs in logs + [line] }^ | |
} | |
} | |
func lookedUpProfile<F: Database & Logger>(_ environment: F) -> Task<UserProfile> { | |
return Task<UserProfile>.binding( | |
{ environment.database.lookup(id: "abc") }, | |
{ profile in environment.logger.info(profile.name) }, | |
{ profile, _ in yield(profile) })^ | |
} | |
struct TestEnvironment: Database, Logger { | |
let database: DatabaseService | |
let logger: LoggerService | |
} | |
func testScenario(_ state: DatabaseTestService.State, _ eff: (TestEnvironment) -> Task<UserProfile>) -> Task<(UserProfile, DatabaseTestService.State, [String])> { | |
let databaseRef = Ref<ForTask, DatabaseTestService.State>.unsafe(state) | |
let loggerRef = Ref<ForTask, [String]>.unsafe([]) | |
let environment = TestEnvironment(database: DatabaseTestService(ref: databaseRef), | |
logger: LoggerTestService(ref: loggerRef)) | |
return ForTask.binding( | |
{ eff(environment) }, | |
{ _ in databaseRef.get() }, | |
{ _, _ in loggerRef.get() }, | |
{ profile, state, logs in yield((profile, state, logs)) })^ | |
} | |
class Run { | |
static func main() { | |
let program = testScenario(DatabaseTestService.State(map: ["abc": UserProfile(name: "testName")], ops: []), lookedUpProfile) | |
program.unsafeRunAsync { either in | |
either.fold( | |
{ error in print(error) }, | |
{ value in | |
print("User Profile: \(value.0)") | |
print("State: \(value.1)") | |
print("Logs: \(value.2)") | |
}) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment