例如:我想在非生产环境下更改 LocalDateTime.now() 返回结果, 我尝试调用以下代码,但出现错误:
@Test
public void test3() {
System.out.println(LocalDateTime.now());
Instrumentation instrumentation = ByteBuddyAgent.install();
Class<?>[] cls = instrumentation.getAllLoadedClasses();
for (Class<?> cl : cls) {
if ("java.time.LocalDateTime".equals(cl.getName())) {
try {
LocalDateTime mockData = LocalDateTime.of(2020, 1, 1, 0, 0, 0, 0);
byte[] bytes = new ByteBuddy()
.redefine(LocalDateTime.class)
.method(ElementMatchers.named("now").and(ElementMatchers.takesNoArguments()))
.intercept(FixedValue.value(mockData))
.make()
.getBytes();
ClassDefinition classDefinition = new ClassDefinition(cl, bytes);
instrumentation.redefineClasses(classDefinition);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
System.out.println(LocalDateTime.now());
}
2024-03-03T15:33:59.291699 java.lang.UnsupportedOperationException:类重定义失败:尝试更改架构(添加/删除字段) 在 java.instrument/sun.instrument.InstrumentationImpl.redefineClasses0(本机方法) 在 java.instrument/sun.instrument.InstrumentationImpl.redefineClasses(InstrumentationImpl.java:195) 在 com.yi.component.test.agent.bytecode.ByteBuddyTest.test3(ByteBuddyTest.java:76) 在 java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(本机方法) 在 java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) 在java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 在 java.base/java.lang.reflect.Method.invoke(Method.java:568) 在 org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) 在 org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) 在 org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) 在 org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) 在 org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) 在 org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) 在 org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) 在org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) 在org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) 在 org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) 在 org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) 在 org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) 在 org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) 在 org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) 在 org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) 在 org.junit.runners.ParentRunner.run(ParentRunner.java:413) 在 org.junit.runner.JUnitCore.run(JUnitCore.java:137) 在com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) 在 com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) 在com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) 在 com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) 在 com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232) 在 com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
我想在非生产环境中更改 LocalDateTime.now() 返回结果
听起来你真正想做的是能够控制测试的时间,这是非常明智的。
更简单的方法是使用依赖注入:不要让您想要测试的类了解系统时钟,而是传入时钟,以便您可以用您在测试中控制的时钟替换它。
举个例子,假设您需要计算逾期借出的物品(例如图书馆或其他物品)的罚款。您的业务规则可能是:
以下是借出物品的定义:
import java.time.LocalDate;
public record LoanedItem(String description, LocalDate dueOn) {}
以及表达行为的测试用例:
import org.junit.jupiter.api.Test;
import java.time.Clock;
import java.time.LocalDate;
import java.time.ZoneOffset;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class FineCalculatorTest {
@Test
public void the_fine_is_1_for_every_day_the_item_is_overdue() {
LocalDate dueOn = LocalDate.of(2024, 2, 1);
LoanedItem loanedItem = new LoanedItem("Lawnmower", dueOn);
int daysOverdue = 2;
LocalDate today = dueOn.plusDays(daysOverdue);
Clock clock = Clock.fixed(today.atStartOfDay().toInstant(ZoneOffset.UTC), ZoneOffset.UTC);
FineCalculator fineCalculator = new FineCalculator(clock);
long fine = fineCalculator.fineFor(loanedItem);
assertEquals(2, fine);
}
@Test
public void the_fine_is_0_if_the_item_is_not_overdue() {
LocalDate dueOn = LocalDate.of(2024, 2, 1);
LoanedItem loanedItem = new LoanedItem("Lawnmower", dueOn);
LocalDate today = dueOn.minusDays(7);
Clock clock = Clock.fixed(today.atStartOfDay().toInstant(ZoneOffset.UTC), ZoneOffset.UTC);
FineCalculator fineCalculator = new FineCalculator(clock);
long fine = fineCalculator.fineFor(loanedItem);
assertEquals(0, fine);
}
}
要注意的关键是
FineCalculator
将 java.time.Clock
的实例作为构造函数参数。
Clock.fixed
创建一个 Clock
,其时间固定为您给出的时间。在第一个测试中,我创建了一个固定在项目到期日后两天的时钟:
int daysOverdue = 2;
LocalDate today = dueOn.plusDays(daysOverdue);
Clock clock = Clock.fixed(today.atStartOfDay().toInstant(ZoneOffset.UTC), ZoneOffset.UTC);
所以我们预计罚款 2。
第二种情况类似:我通过了一个固定在预产期前7天的时钟,所以我们预计罚款为0。
FineCalculator
的实现然后使用时钟来计算今天是什么并计算罚款:
import java.time.Clock;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
public class FineCalculator {
private Clock clock;
public FineCalculator(Clock clock) {
this.clock = clock;
}
public long fineFor(LoanedItem loanedItem) {
LocalDate today = clock.instant().atZone(ZoneOffset.UTC).toLocalDate();
long daysOverdue = ChronoUnit.DAYS.between(loanedItem.dueOn(), today);
if (daysOverdue < 0) {
daysOverdue = 0;
}
long fine = 1 * daysOverdue;
return fine;
}
}
当您在生产代码中创建
FineCalculator
时,您将传递系统时钟。这是一个示例main
:
import java.time.Clock;
import java.time.LocalDate;
import java.time.ZoneOffset;
public class Main {
public static void main(String[] args) {
LocalDate dueOn = LocalDate.of(2024, 9, 1);
LoanedItem item = new LoanedItem("Screwdriver", dueOn);
Clock clock = Clock.system(ZoneOffset.UTC);
FineCalculator fineCalculator = new FineCalculator(clock);
System.out.println("Loaned item: " + item);
System.out.println("Fine is: " + fineCalculator.fineFor(item));
}
}