Scala单元测试中如何Mock DateTime.now()消除时间依赖?
如何Mock DateTime.now()编写稳定的Scala单元测试
当然可以Mock DateTime.now(),让测试结果不受运行时间影响,这里有两种常用方案:
1. 依赖注入(推荐方案)
重构原函数,把获取当前时间的逻辑抽成独立的依赖组件,这样测试时可以传入固定的时间实现,完全避免对系统时间的依赖:
重构后的业务代码
import com.github.nscala_time.time.Imports.{DateTime, richReadableInstant} // 定义时间提供者接口 trait TimeProvider { def now(): DateTime } // 默认实现:使用系统当前时间 class DefaultTimeProvider extends TimeProvider { override def now(): DateTime = DateTime.now() } // 修改函数,默认使用默认提供者,测试时可传入自定义实现 def myFunction(ts: Long, timeProvider: TimeProvider = new DefaultTimeProvider): Long = { (new DateTime(ts) to timeProvider.now()).toDurationMillis }
对应的单元测试
import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import com.github.nscala_time.time.Imports.DateTime class MyFunctionTest extends AnyFlatSpec with Matchers { it should "计算两个时间点的正确间隔" in { // 固定当前时间为2024-01-01 12:00:00 val fixedNow = DateTime.parse("2024-01-01T12:00:00") // 测试用的时间戳为1小时前 val testTs = fixedNow.minusHours(1).getMillis // 自定义时间提供者,返回固定时间 val mockTimeProvider = new TimeProvider { override def now(): DateTime = fixedNow } val result = myFunction(testTs, mockTimeProvider) // 验证结果为1小时对应的毫秒数 result shouldBe 3600000 } }
这种方式遵循依赖倒置原则,代码耦合度低,测试逻辑清晰,也方便后续扩展不同的时间获取逻辑。
2. 使用Mock工具Mock静态方法
如果不想修改原有业务代码,可以用PowerMock配合Mockito来Mock DateTime.now()的静态调用。不过静态Mock会修改类的字节码,可能导致测试隔离问题,仅作为无重构时的备选方案:
首先添加依赖(build.sbt)
libraryDependencies ++= Seq( "org.scalatest" %% "scalatest" % "3.2.15" % Test, "org.powermock" % "powermock-module-junit4" % "2.0.9" % Test, "org.powermock" % "powermock-api-mockito2" % "2.0.9" % Test, "org.mockito" %% "mockito-scala" % "1.17.12" % Test )
编写单元测试
import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import org.powermock.api.mockito.PowerMockito import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner import org.junit.runner.RunWith import com.github.nscala_time.time.Imports.DateTime @RunWith(classOf[PowerMockRunner]) // 声明需要Mock的静态类 @PrepareForTest(Array(classOf[DateTime])) class MyFunctionStaticMockTest extends AnyFlatSpec with Matchers { it should "通过Mock静态方法返回固定间隔" in { val fixedNow = DateTime.parse("2024-01-01T12:00:00") val testTs = fixedNow.minusHours(1).getMillis // Mock DateTime的静态方法now() PowerMockito.mockStatic(classOf[DateTime]) PowerMockito.when(DateTime.now()).thenReturn(fixedNow) val result = myFunction(testTs) result shouldBe 3600000 // 验证静态方法被调用,同时清理Mock PowerMockito.verifyStatic(classOf[DateTime]) DateTime.now() } }
内容的提问来源于stack exchange,提问作者bayerb
相关产品推荐
相关产品推荐

