モックのOKとNGなどはテストのファイルで用意する気がしていたが
いろいろとeffect-tsのドキュメントを読むうちにソースのファイルのほうで用意する気がしてきた
モックのOKとNGを両方用意して、それですべてのパターンを簡単にテストできるようになる
これでプログラム構造を確定させてから、モックでない処理を書いていくような気がする
前回
エラー対応追加したもの
get_data_from_domain で Effect.catchTags を使用して
複数個所で発生するエラーを1つで対応している
これによって
OK時の動作をget_data_from_domainの前方で記述し、
NG時の動作をget_data_from_domainの後方で記述するだけでよい
下位のエラーもすべて対応できそう
ngのモックの時にLayer.succeed を使うのが、なんか微妙な感じがする
import { Cache, Context, Data, Duration, Effect, Layer } from "effect"
type type_employee_id = string
type type_department_id = string
class DatabaseError extends Data.TaggedError("DatabaseError")<{
table_name: string
}> {}
class DomainError extends Data.TaggedError("DomainError")<{
reason: string
}> {}
// ---- db : employee ----
interface interface_employee {
employee_id: type_employee_id
employee_name: string
department_id: type_department_id
}
class database_employee extends Context.Tag("database_employee")<
database_employee,
{
readonly get_one_employee: (
employee_id: type_employee_id
) => Effect.Effect<interface_employee, DatabaseError>
}
>() {}
const database_employee_mock_ok = Layer.succeed(
database_employee,
{
get_one_employee: (employee_id: type_employee_id) =>
Effect.gen(function*() {
yield* Effect.log("get_one_employee ok start")
yield* Effect.sleep(Duration.seconds(5))
const data: interface_employee = {
employee_id,
employee_name: "test employee",
department_id: test_department_id
}
yield* Effect.log("get_one_employee end")
return data
})
}
)
const database_employee_mock_ng_database_error = Layer.succeed(
database_employee,
{
get_one_employee: (_: type_employee_id) =>
Effect.gen(function*() {
yield* Effect.log("get_one_employee ng start")
return yield* Effect.fail(new DatabaseError({ table_name: "employee" }))
})
}
)
const employee_cache = Cache.make({
capacity: 100,
timeToLive: Duration.seconds(5),
lookup: (employee_id: type_employee_id) =>
Effect.gen(function*() {
yield* Effect.log("employee_cache lookup start")
const DatabaseEmployee = yield* database_employee
const employee = yield* DatabaseEmployee.get_one_employee(employee_id)
return employee
})
})
// ---- db : department ----
interface interface_department {
department_id: type_department_id
department_name: string
}
class database_department extends Context.Tag("database_department")<
database_department,
{
readonly get_one_department: (
department_id: type_department_id
) => Effect.Effect<interface_department, DatabaseError>
}
>() {}
const database_department_mock_ok = Layer.succeed(
database_department,
{
get_one_department: (department_id: type_department_id) =>
Effect.gen(function*() {
yield* Effect.log("get_one_department ok start")
yield* Effect.sleep(Duration.seconds(3))
const data: interface_department = {
department_id,
department_name: "test department"
}
yield* Effect.log("get_one_department end")
return data
})
}
)
const database_department_mock_ng_database_error = Layer.succeed(
database_department,
{
get_one_department: (_: type_department_id) =>
Effect.gen(function*() {
yield* Effect.log("get_one_department ng start")
return yield* Effect.fail(new DatabaseError({ table_name: "department" }))
})
}
)
const department_cache = Cache.make({
capacity: 100,
timeToLive: Duration.seconds(5),
lookup: (department_id: type_department_id) =>
Effect.gen(function*() {
yield* Effect.log("department_cache lookup start")
const DatabaseDepartment = yield* database_department
const department = yield* DatabaseDepartment.get_one_department(department_id)
return department
})
})
// ---- test data ----
const test_employee_id: type_employee_id = "e_00_test"
const test_department_id: type_department_id = "d_00_test"
// ---- domain -----------------
interface employee_data {
employee: interface_employee
department: interface_department
}
class domain_employee extends Context.Tag("domain_employee")<
domain_employee,
{
readonly get_one_employee_data: (
employee_id: type_employee_id
) => Effect.Effect<employee_data, DatabaseError | DomainError>
}
>() {}
const domain_employee_service = Layer.effect(
domain_employee,
Effect.gen(function*() {
yield* Effect.log("domain_employee_service start")
const CacheEmployee = yield* employee_cache
const CacheDepartment = yield* department_cache
return {
get_one_employee_data: (
employee_id: type_employee_id
) =>
Effect.gen(function*() {
yield* Effect.log("get_one_employee_data start")
const isHitEmployee = yield* CacheEmployee.contains(employee_id)
if (isHitEmployee) yield* Effect.log("employee_data from cache")
const employee = yield* CacheEmployee.get(employee_id)
const isHitDepartment = yield* CacheDepartment.contains(employee.department_id)
if (isHitDepartment) yield* Effect.log("department_data from cache")
const department = yield* CacheDepartment.get(employee.department_id)
const result: employee_data = {
employee,
department
}
return result
})
}
})
)
const domain_employee_service_ng = Layer.effect(
domain_employee,
Effect.gen(function*() {
yield* Effect.log("domain_employee_service_ng start")
return {
get_one_employee_data: (
_: type_employee_id
) =>
Effect.gen(function*() {
yield* Effect.log("get_one_employee_data start")
return yield* Effect.fail(new DomainError({ reason: "原因不明のエラーが発生" }))
})
}
})
)
const get_data_from_domain = Effect.gen(function*() {
yield* Effect.log("get_data_from_domain start")
const DomainEmployee = yield* domain_employee
const result_employee = yield* DomainEmployee.get_one_employee_data(test_employee_id)
yield* Effect.log(result_employee)
const result_employee_2 = yield* DomainEmployee.get_one_employee_data(test_employee_id)
yield* Effect.log(result_employee_2)
yield* Effect.sleep(Duration.seconds(5))
const result_employee_3 = yield* DomainEmployee.get_one_employee_data(test_employee_id)
yield* Effect.log(result_employee_3)
}).pipe(
Effect.catchTags({
DatabaseError: (error) => Effect.logError(`DatabaseErrorが発生しました : ${error.table_name}`),
DomainError: (error) => Effect.logError(`DomainErrorが発生しました : ${error.reason}`)
})
)
// ---- layer ----
const domain_service_ok = Effect.provide(get_data_from_domain, domain_employee_service)
const domain_service_ng = Effect.provide(get_data_from_domain, domain_employee_service_ng)
const service_mock_ok = Layer.mergeAll(database_employee_mock_ok, database_department_mock_ok)
const service_mock_ng_employee = Layer.mergeAll(
database_employee_mock_ng_database_error,
database_department_mock_ok
)
const service_mock_ng_department = Layer.mergeAll(
database_employee_mock_ok,
database_department_mock_ng_database_error
)
// ---- program ----
const program_domain_mock_ok = domain_service_ok.pipe(Effect.provide(service_mock_ok))
const program_domain_mock_ng = domain_service_ng.pipe(Effect.provide(service_mock_ok))
const program_domain_mock_ng_employee = domain_service_ok.pipe(Effect.provide(service_mock_ng_employee))
const program_domain_mock_ng_department = domain_service_ok.pipe(Effect.provide(service_mock_ng_department))
// ---- Entry Point ----
Effect.runPromise(program_domain_mock_ok).catch((reason) => {
console.log(reason)
})
OK時
最後を以下にする
Effect.runPromise(program_domain_mock_ok).catch((reason) => {
console.log(reason)
})
実行結果
timestamp=2025-09-27T13:43:46.004Z level=INFO fiber=#0 message="domain_employee_service start"
timestamp=2025-09-27T13:43:46.008Z level=INFO fiber=#0 message="get_data_from_domain start"
timestamp=2025-09-27T13:43:46.009Z level=INFO fiber=#0 message="get_one_employee_data start"
timestamp=2025-09-27T13:43:46.012Z level=INFO fiber=#0 message="employee_cache lookup start"
timestamp=2025-09-27T13:43:46.013Z level=INFO fiber=#0 message="get_one_employee ok start"
timestamp=2025-09-27T13:43:51.023Z level=INFO fiber=#0 message="get_one_employee end"
timestamp=2025-09-27T13:43:51.030Z level=INFO fiber=#0 message="department_cache lookup start"
timestamp=2025-09-27T13:43:51.033Z level=INFO fiber=#0 message="get_one_department ok start"
timestamp=2025-09-27T13:43:54.044Z level=INFO fiber=#0 message="get_one_department end"
timestamp=2025-09-27T13:43:54.045Z level=INFO fiber=#0 message="{
\"employee\": {
\"employee_id\": \"e_00_test\",
\"employee_name\": \"test employee\",
\"department_id\": \"d_00_test\"
},
\"department\": {
\"department_id\": \"d_00_test\",
\"department_name\": \"test department\"
}
}"
timestamp=2025-09-27T13:43:54.046Z level=INFO fiber=#0 message="get_one_employee_data start"
timestamp=2025-09-27T13:43:54.046Z level=INFO fiber=#0 message="employee_data from cache"
timestamp=2025-09-27T13:43:54.047Z level=INFO fiber=#0 message="department_data from cache"
timestamp=2025-09-27T13:43:54.048Z level=INFO fiber=#0 message="{
\"employee\": {
\"employee_id\": \"e_00_test\",
\"employee_name\": \"test employee\",
\"department_id\": \"d_00_test\"
},
\"department\": {
\"department_id\": \"d_00_test\",
\"department_name\": \"test department\"
}
}"
timestamp=2025-09-27T13:43:59.052Z level=INFO fiber=#0 message="get_one_employee_data start"
timestamp=2025-09-27T13:43:59.055Z level=INFO fiber=#0 message="employee_data from cache"
timestamp=2025-09-27T13:43:59.059Z level=INFO fiber=#0 message="employee_cache lookup start"
timestamp=2025-09-27T13:43:59.061Z level=INFO fiber=#0 message="get_one_employee ok start"
timestamp=2025-09-27T13:44:04.070Z level=INFO fiber=#0 message="get_one_employee end"
timestamp=2025-09-27T13:44:04.073Z level=INFO fiber=#0 message="department_data from cache"
timestamp=2025-09-27T13:44:04.076Z level=INFO fiber=#0 message="department_cache lookup start"
timestamp=2025-09-27T13:44:04.079Z level=INFO fiber=#0 message="get_one_department ok start"
timestamp=2025-09-27T13:44:07.085Z level=INFO fiber=#0 message="get_one_department end"
timestamp=2025-09-27T13:44:07.085Z level=INFO fiber=#0 message="{
\"employee\": {
\"employee_id\": \"e_00_test\",
\"employee_name\": \"test employee\",
\"department_id\": \"d_00_test\"
},
\"department\": {
\"department_id\": \"d_00_test\",
\"department_name\": \"test department\"
}
}"
メイン処理で NG時
最後を以下にする
Effect.runPromise(program_domain_mock_ng).catch((reason) => {
console.log(reason)
})
実行結果
timestamp=2025-09-27T13:59:10.172Z level=INFO fiber=#0 message="domain_employee_service_ng start"
timestamp=2025-09-27T13:59:10.178Z level=INFO fiber=#0 message="get_data_from_domain start"
timestamp=2025-09-27T13:59:10.179Z level=INFO fiber=#0 message="get_one_employee_data start"
timestamp=2025-09-27T13:59:10.181Z level=ERROR fiber=#0 message="DomainErrorが発生しました : 原因不明のエラーが発生"
データベース(employee)で NG時
最後を以下にする
Effect.runPromise(program_domain_mock_ng_employee).catch((reason) => {
console.log(reason)
})
実行結果
timestamp=2025-09-27T14:00:05.968Z level=INFO fiber=#0 message="domain_employee_service start"
timestamp=2025-09-27T14:00:05.976Z level=INFO fiber=#0 message="get_data_from_domain start"
timestamp=2025-09-27T14:00:05.977Z level=INFO fiber=#0 message="get_one_employee_data start"
timestamp=2025-09-27T14:00:05.980Z level=INFO fiber=#0 message="employee_cache lookup start"
timestamp=2025-09-27T14:00:05.981Z level=INFO fiber=#0 message="get_one_employee ng start"
timestamp=2025-09-27T14:00:05.984Z level=ERROR fiber=#0 message="DatabaseErrorが発生しました : employee"
データベース(department)で NG時
最後を以下にする
Effect.runPromise(program_domain_mock_ng_employee).catch((reason) => {
console.log(reason)
})
実行結果
timestamp=2025-09-27T14:00:41.821Z level=INFO fiber=#0 message="domain_employee_service start"
timestamp=2025-09-27T14:00:41.826Z level=INFO fiber=#0 message="get_data_from_domain start"
timestamp=2025-09-27T14:00:41.827Z level=INFO fiber=#0 message="get_one_employee_data start"
timestamp=2025-09-27T14:00:41.829Z level=INFO fiber=#0 message="employee_cache lookup start"
timestamp=2025-09-27T14:00:41.831Z level=INFO fiber=#0 message="get_one_employee ok start"
timestamp=2025-09-27T14:00:46.834Z level=INFO fiber=#0 message="get_one_employee end"
timestamp=2025-09-27T14:00:46.835Z level=INFO fiber=#0 message="department_cache lookup start"
timestamp=2025-09-27T14:00:46.836Z level=INFO fiber=#0 message="get_one_department ng start"
timestamp=2025-09-27T14:00:46.838Z level=ERROR fiber=#0 message="DatabaseErrorが発生しました : department"