Spring WebFlux 中 reactor.core.publisher.MonoWhen$WhenCoordinator 中 FilePart 上的 NPE 模拟
NPE on FilePart mock in Spring WebFlux at reactor.core.publisher.MonoWhen$WhenCoordinator
对于我遇到的以下问题的任何提示,我将不胜感激。在 Spring Reactive WebFlux
应用程序中对多部分文件上传服务方法进行单元测试时,我得到 reactor.core.publisher.MonoWhen$WhenCoordinator
的 NPE 如下
java.lang.NullPointerException
at reactor.core.publisher.MonoWhen$WhenCoordinator.subscribe(MonoWhen.java:149)
下面也列出了完整的日志。
测试:
@RunWith(SpringRunner.class)
@SpringBootTest
public class FileServiceTest2 {
@MockBean
private UploadedImageRepository uploadedImageRepository;
...
@Test
public void assembleImageTest() {
UploadedImage ui1 = new UploadedImage("1", "ui1.png");
UploadedImage ui2 = new UploadedImage("2", "ui2.png");
FilePart filePart1 = mock(FilePart.class);
FilePart filePart2 = mock(FilePart.class);
given(this.uploadedImageRepository.save(ui1))
.willReturn(Mono.just(ui1));
given(this.uploadedImageRepository.save(ui2))
.willReturn(Mono.just(ui2));
given(this.uploadedImageRepository.findAll())
.willReturn(Flux.just(ui1, ui2));
given(filePart1.filename())
.willReturn(ui1.getImageName());
given(filePart1.transferTo(any()))
.willReturn(Mono.empty());
given(filePart2.filename())
.willReturn(ui2.getImageName());
given(filePart2.transferTo(any()))
.willReturn(Mono.empty());
Flux<FilePart> files = Flux.just(filePart1, filePart2);
StepVerifier.create(this.uploadService.createFile(files))
.verifyComplete();
}
正在测试中:
@Service
public class UploadService {
Mono<Void> createFile(Flux<FilePart> fileParts) {
return fileParts.flatMap(part -> {
Mono<UploadedImage> savedToDBImage = this.uploadedImageRepository.save(
new UploadedImage(UUID.randomUUID().toString(), part.filename()))
.log("createFile-fileSavedToDB"); // NPE!
Mono<Void> copiedFile = Mono.just(Paths.get(UPLOAD_URL, part.filename()).toFile())
.log("createFile-pathAssembled")
.doOnNext(destinationFile -> {
try {
destinationFile.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
})
.log("createFile-fileAssembled")
.flatMap(part::transferTo)
.log("createFile-fileCopied");
return Mono.when(savedToDBImage, copiedFile)
.log("createFile-monoWhen");
})
.log("createFile-flatMap")
.then()
.log("createFile-done");
}
UploadedImage
class(龙目岛):
@Data
@RequiredArgsConstructor
@NoArgsConstructor
public class UploadedImage {
@NonNull private String id;
@NonNull private String imageName;
}
SpringData 反应存储库:
@Repository
public interface UploadedImageRepository extends ReactiveCrudRepository<UploadedImage, String> {
}
日志如下:
java.lang.NullPointerException
at reactor.core.publisher.MonoWhen$WhenCoordinator.subscribe(MonoWhen.java:149)
at reactor.core.publisher.MonoWhen.subscribe(MonoWhen.java:99)
at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76)
at reactor.core.publisher.MonoLogFuseable.subscribe(MonoLogFuseable.java:53)
at reactor.core.publisher.Mono.subscribe(Mono.java:3080)
at reactor.core.publisher.FluxFlatMap$FlatMapMain.onNext(FluxFlatMap.java:372)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onNext(FluxPeekFuseable.java:198)
at reactor.core.publisher.FluxArray$ArraySubscription.slowPath(FluxArray.java:118)
at reactor.core.publisher.FluxArray$ArraySubscription.request(FluxArray.java:91)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.request(FluxPeekFuseable.java:138)
at reactor.core.publisher.FluxFlatMap$FlatMapMain.onSubscribe(FluxFlatMap.java:332)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onSubscribe(FluxPeekFuseable.java:172)
at reactor.core.publisher.FluxArray.subscribe(FluxArray.java:53)
at reactor.core.publisher.FluxArray.subscribe(FluxArray.java:59)
at reactor.core.publisher.FluxLogFuseable.subscribe(FluxLogFuseable.java:53)
at reactor.core.publisher.FluxFlatMap.subscribe(FluxFlatMap.java:97)
at reactor.core.publisher.FluxLog.subscribe(FluxLog.java:50)
at reactor.core.publisher.MonoIgnoreElements.subscribe(MonoIgnoreElements.java:37)
at reactor.core.publisher.MonoLog.subscribe(MonoLog.java:51)
at reactor.core.publisher.Mono.subscribe(Mono.java:3080)
at reactor.test.DefaultStepVerifierBuilder$DefaultStepVerifier.verify(DefaultStepVerifierBuilder.java:728)
at reactor.test.DefaultStepVerifierBuilder$DefaultStepVerifier.verify(DefaultStepVerifierBuilder.java:700)
at reactor.test.DefaultStepVerifierBuilder.verifyComplete(DefaultStepVerifierBuilder.java:566)
at pb.sl.UploadService.createFile(FileServiceTest2.java:112)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access[=17=]0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
仔细观察,我认为您没有按预期模拟您的存储库,并且在您的测试中存储库调用 returns null。
您使用的不是 Mockito 参数匹配器,而是参数的具体实例。稍后在您的服务实现中,调用此方法,但使用不同的实例。我的猜测是 Mockito 正在使用 equals
来检查给定的是否匹配那个模拟调用 - 也许你的 UploadedImage
在这方面可以改进?
given(this.uploadedImageRepository.save(ui1))
应该是:
given(this.uploadedImageRepository.save(any()))
.willAnswer(invocation -> Mono.just(invocation.getArgument(0)))
对于我遇到的以下问题的任何提示,我将不胜感激。在 Spring Reactive WebFlux
应用程序中对多部分文件上传服务方法进行单元测试时,我得到 reactor.core.publisher.MonoWhen$WhenCoordinator
的 NPE 如下
java.lang.NullPointerException
at reactor.core.publisher.MonoWhen$WhenCoordinator.subscribe(MonoWhen.java:149)
下面也列出了完整的日志。
测试:
@RunWith(SpringRunner.class)
@SpringBootTest
public class FileServiceTest2 {
@MockBean
private UploadedImageRepository uploadedImageRepository;
...
@Test
public void assembleImageTest() {
UploadedImage ui1 = new UploadedImage("1", "ui1.png");
UploadedImage ui2 = new UploadedImage("2", "ui2.png");
FilePart filePart1 = mock(FilePart.class);
FilePart filePart2 = mock(FilePart.class);
given(this.uploadedImageRepository.save(ui1))
.willReturn(Mono.just(ui1));
given(this.uploadedImageRepository.save(ui2))
.willReturn(Mono.just(ui2));
given(this.uploadedImageRepository.findAll())
.willReturn(Flux.just(ui1, ui2));
given(filePart1.filename())
.willReturn(ui1.getImageName());
given(filePart1.transferTo(any()))
.willReturn(Mono.empty());
given(filePart2.filename())
.willReturn(ui2.getImageName());
given(filePart2.transferTo(any()))
.willReturn(Mono.empty());
Flux<FilePart> files = Flux.just(filePart1, filePart2);
StepVerifier.create(this.uploadService.createFile(files))
.verifyComplete();
}
正在测试中:
@Service
public class UploadService {
Mono<Void> createFile(Flux<FilePart> fileParts) {
return fileParts.flatMap(part -> {
Mono<UploadedImage> savedToDBImage = this.uploadedImageRepository.save(
new UploadedImage(UUID.randomUUID().toString(), part.filename()))
.log("createFile-fileSavedToDB"); // NPE!
Mono<Void> copiedFile = Mono.just(Paths.get(UPLOAD_URL, part.filename()).toFile())
.log("createFile-pathAssembled")
.doOnNext(destinationFile -> {
try {
destinationFile.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
})
.log("createFile-fileAssembled")
.flatMap(part::transferTo)
.log("createFile-fileCopied");
return Mono.when(savedToDBImage, copiedFile)
.log("createFile-monoWhen");
})
.log("createFile-flatMap")
.then()
.log("createFile-done");
}
UploadedImage
class(龙目岛):
@Data
@RequiredArgsConstructor
@NoArgsConstructor
public class UploadedImage {
@NonNull private String id;
@NonNull private String imageName;
}
SpringData 反应存储库:
@Repository
public interface UploadedImageRepository extends ReactiveCrudRepository<UploadedImage, String> {
}
日志如下:
java.lang.NullPointerException
at reactor.core.publisher.MonoWhen$WhenCoordinator.subscribe(MonoWhen.java:149)
at reactor.core.publisher.MonoWhen.subscribe(MonoWhen.java:99)
at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76)
at reactor.core.publisher.MonoLogFuseable.subscribe(MonoLogFuseable.java:53)
at reactor.core.publisher.Mono.subscribe(Mono.java:3080)
at reactor.core.publisher.FluxFlatMap$FlatMapMain.onNext(FluxFlatMap.java:372)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onNext(FluxPeekFuseable.java:198)
at reactor.core.publisher.FluxArray$ArraySubscription.slowPath(FluxArray.java:118)
at reactor.core.publisher.FluxArray$ArraySubscription.request(FluxArray.java:91)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.request(FluxPeekFuseable.java:138)
at reactor.core.publisher.FluxFlatMap$FlatMapMain.onSubscribe(FluxFlatMap.java:332)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onSubscribe(FluxPeekFuseable.java:172)
at reactor.core.publisher.FluxArray.subscribe(FluxArray.java:53)
at reactor.core.publisher.FluxArray.subscribe(FluxArray.java:59)
at reactor.core.publisher.FluxLogFuseable.subscribe(FluxLogFuseable.java:53)
at reactor.core.publisher.FluxFlatMap.subscribe(FluxFlatMap.java:97)
at reactor.core.publisher.FluxLog.subscribe(FluxLog.java:50)
at reactor.core.publisher.MonoIgnoreElements.subscribe(MonoIgnoreElements.java:37)
at reactor.core.publisher.MonoLog.subscribe(MonoLog.java:51)
at reactor.core.publisher.Mono.subscribe(Mono.java:3080)
at reactor.test.DefaultStepVerifierBuilder$DefaultStepVerifier.verify(DefaultStepVerifierBuilder.java:728)
at reactor.test.DefaultStepVerifierBuilder$DefaultStepVerifier.verify(DefaultStepVerifierBuilder.java:700)
at reactor.test.DefaultStepVerifierBuilder.verifyComplete(DefaultStepVerifierBuilder.java:566)
at pb.sl.UploadService.createFile(FileServiceTest2.java:112)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access[=17=]0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
仔细观察,我认为您没有按预期模拟您的存储库,并且在您的测试中存储库调用 returns null。
您使用的不是 Mockito 参数匹配器,而是参数的具体实例。稍后在您的服务实现中,调用此方法,但使用不同的实例。我的猜测是 Mockito 正在使用 equals
来检查给定的是否匹配那个模拟调用 - 也许你的 UploadedImage
在这方面可以改进?
given(this.uploadedImageRepository.save(ui1))
应该是:
given(this.uploadedImageRepository.save(any()))
.willAnswer(invocation -> Mono.just(invocation.getArgument(0)))