如何在JUnit测试中用Mockito Mock Google Guice依赖注入?
问题根源:Guice注入直接覆盖了Mockito的Mock
兄弟,你的Mock不生效的核心原因太明显了——在ErrorReproduction的start方法里,你手动创建了Guice Injector并调用injectMembers(this),这会直接覆盖Mockito通过@InjectMocks注入的dataCreation mock对象。当Verticle启动时,Guice会重新把真实的DataCreation实例塞进去,你之前用Mockito配置的mock行为自然就失效了。
解决方案1:重构Verticle,让依赖注入可控
最根本的解决办法是把Verticle里硬编码创建Injector的逻辑改掉,让外部能控制注入的依赖。这样测试时我们就能把mock对象塞进去,而不是让Guice自己创建真实对象。
修改Verticle代码
package com.testproject.starter.verticles; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.name.Named; import com.testproject.starter.model.DataCreationModel; import com.testproject.starter.services.DataCreation; import io.vertx.core.AbstractVerticle; import io.vertx.core.Future; import io.vertx.core.json.Json; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; public class ErrorReproduction extends AbstractVerticle { @Inject @Named("dataCreation") DataCreation dataCreation; private final Injector injector; // 测试用构造函数:允许传入自定义Injector @Inject public ErrorReproduction(Injector injector) { this.injector = injector; } // 生产环境用无参构造函数,创建默认Injector public ErrorReproduction() { this(Guice.createInjector(new DataCreationModel())); } @Override public void start(Future<Void> startFuture) throws Exception { // 用传入的Injector完成注入,不再硬编码创建 injector.injectMembers(this); int port = config().getInteger("http.port", 8080); Router router = Router.router(vertx); router.route("/api/getAll").handler(this::getAll); vertx.createHttpServer().requestHandler(router::accept) .listen(port, result -> startFuture.complete()); } public void getAll(RoutingContext routingContext) { routingContext.response().putHeader("content-type", "application/json") .end(Json.encodePrettily(dataCreation.getData().values())); } }
修改测试代码
现在测试时我们可以创建一个包含mock对象的Guice Module,把它传入Verticle,这样Guice就会注入我们的mock而不是真实对象:
package com.testproject.starter.verticles; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.name.Names; import com.testproject.starter.dao.DataCreationDao; import com.testproject.starter.ppojo.Whisky; import com.testproject.starter.services.DataCreation; import io.vertx.core.DeploymentOptions; import io.vertx.core.Vertx; import io.vertx.core.http.HttpClient; import io.vertx.core.json.JsonObject; import io.vertx.ext.unit.Async; import io.vertx.ext.unit.TestContext; import io.vertx.ext.unit.junit.RunTestOnContext; import io.vertx.ext.unit.junit.VertxUnitRunner; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.io.IOException; import java.net.ServerSocket; import java.util.LinkedHashMap; import java.util.Map; import static org.mockito.Mockito.when; @RunWith(VertxUnitRunner.class) public class ErrorReproductionTest { Vertx vertx; int port; @Mock private DataCreation dataCreation; @Mock private DataCreationDao dataCreationDao; @Rule public RunTestOnContext rule = new RunTestOnContext(); @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); private ErrorReproduction errVertical; @Before public void before(TestContext context) throws IOException { ServerSocket socket = new ServerSocket(0); port = socket.getLocalPort(); socket.close(); // 创建测试用的Guice Module,绑定我们的mock对象 Injector testInjector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { bind(DataCreation.class).annotatedWith(Names.named("dataCreation")).toInstance(dataCreation); bind(DataCreationDao.class).toInstance(dataCreationDao); } }); // 用测试Injector创建Verticle errVertical = new ErrorReproduction(testInjector); DeploymentOptions options = new DeploymentOptions() .setConfig(new JsonObject().put("http.port", port)); vertx = Vertx.vertx(); rule.vertx().deployVerticle(errVertical, options, context.asyncAssertSuccess()); } @After public void after(TestContext context) { vertx.close(context.asyncAssertSuccess()); } @Test public void testGetAll(TestContext context){ Map<Integer, Whisky> dataSets = new LinkedHashMap<>(); Whisky w1 = new Whisky("Bowmore 15 Years Laimrig", "Scotland, Islay"); Whisky w2 = new Whisky("Talisker 57° kya h", "Scotland, Island"); Async async = context.async(); dataSets.put(w1.getId(), w1); dataSets.put(w2.getId(), w2); // 配置mock的返回值 when(dataCreationDao.getData()).thenReturn(dataSets); when(dataCreation.getData()).thenReturn(dataSets); HttpClient client = vertx.createHttpClient(); client.getNow(port, "localhost", "/api/getAll", response -> { response.bodyHandler(body -> { // 这里可以加断言验证返回结果是否符合预期 context.assertEquals(Json.encodePrettily(dataSets.values()), body.toString()); client.close(); async.complete(); }); }); } }
解决方案2:用Mockito-Guice简化测试(可选)
如果你不想大改Verticle的构造函数,可以用mockito-guice扩展,它能让Guice直接使用Mockito的mock对象。首先加个Maven依赖:
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-guice</artifactId> <version>4.11.0</version> <scope>test</scope> </dependency>
然后测试时可以直接用@Inject和Mockito的mock结合,不过本质上还是需要让Verticle使用测试用的Guice Module,核心思路和方案1一致,只是写法更简洁。
关键提醒
- 别在Verticle里硬编码创建Injector:这是测试的大忌,会把依赖写死,完全失去了依赖注入的灵活性。
- Guice注入优先级高于Mockito:只要你调用了Guice的
injectMembers,之前Mockito注入的对象就会被覆盖,这点一定要记住。 - 测试环境用专门的Module:测试时一定要用测试Module绑定mock,和生产环境的Module隔离开,避免真实对象干扰测试。
内容的提问来源于stack exchange,提问作者Ashutosh
相关产品推荐
相关产品推荐

