🕰️
Rustで時間の関わるコードを簡単にテストする
ちゃんとやる場合はchrono::TimeZone
を実装すると思うが手間なので、自分の書く範囲であれば次のようにnow() -> chrono::DateTime<chrono::Utc>
を持つ別物のmock_chrono::Utc
を置くのが良さそう。
時分秒を指定したりしてセットできると便利だろう。
#[cfg(test)]
mod mock_chrono {
use chrono::{DateTime, NaiveDate};
use std::cell::Cell;
thread_local! {
static TIMESTAMP: Cell<i64> = const { Cell::new(0) };
}
pub struct Utc;
impl Utc {
pub fn now() -> DateTime<chrono::Utc> {
TIMESTAMP
.with(|timestamp| DateTime::<chrono::Utc>::from_timestamp(timestamp.get(), 0))
.expect("invalid timestamp")
}
}
pub fn set_timestamp(h: u32, m: u32, s: u32) {
let timestamp = NaiveDate::from_ymd_opt(2025, 1, 1)
.unwrap()
.and_hms_opt(h, m, s)
.unwrap()
.and_utc()
.timestamp();
TIMESTAMP.with(|ts| ts.set(timestamp));
}
}
テスト用なのでunwrap()
祭り。
あとは状況にも依るが、now()
以降ではchrono::Utc
を扱うわけなので、一々chrono::Utc
と書くよりはnow()
を呼ぶ時だけ条件付きコンパイルするのが綺麗ではなかろうか。
use chrono::{DateTime, Utc};
fn current_time() -> DateTime<Utc> {
#[cfg(test)]
let now = mock_chrono::Utc::now();
#[cfg(not(test))]
let now = Utc::now();
now
}
あまりモックという感じではなくなるけれども。
Discussion