【问题标题】:Overriding a dependency in a Micronaut test覆盖 Micronaut 测试中的依赖项
【发布时间】:2019-04-07 11:54:12
【问题描述】:

我正在测试一个注入了 bean 的 Micronaut 类。在我的测试中,我提供了一个 @MockBean 类来覆盖它。但是,似乎 Micronaut 仍然注入了真正的依赖项。

@MicronautTest
public class ClassUnderTestTest {

    @Inject ClassUnderTest classUnderTest;

    @Test
    public void test() {

    }

    @MockBean
    Dependency dependency() {
        return mock(Dependency.class);
    }

}

我在 Github 上上传了一个最低限度的复制品:https://github.com/crummy/micronaut-test-dependencies。真正的依赖会引发异常,测试也会。由于我的@MockBean,我没想到会发生这种情况。

如果我将注释更改为@MockBean(Dependency.class),则会收到此错误:Message: No bean of type [di.failure.example.Dependency] exists。这对我来说似乎更令人困惑 - 现在它不能解决我的真实或模拟依赖关系?

【问题讨论】:

    标签: java dependency-injection micronaut


    【解决方案1】:

    如果您在ClassUnderTest 中的依赖项由接口表示,则使用@MockBean 注释注入模拟bean 有效。假设Dependency 是一个简单的界面,例如:

    package di.failure.example;
    
    public interface Dependency {
        void run();
    }
    

    您的应用程序可能会为此接口提供一个名为 DependencyImpl 的实现:

    package di.failure.example;
    
    import javax.inject.Singleton;
    
    @Singleton
    public class DependencyImpl implements Dependency {
        @Override
        public void run() {
            throw new RuntimeException("I don't want this to load!");
        }
    }
    

    现在,出于测试目的,您可以定义一个替换 DependencyImpl 的模拟:

    package di.failure.example;
    
    import io.micronaut.test.annotation.MicronautTest;
    import io.micronaut.test.annotation.MockBean;
    import org.junit.jupiter.api.Test;
    
    import javax.inject.Inject;
    
    import static org.mockito.Mockito.mock;
    
    @MicronautTest
    public class ClassUnderTestTest {
    
        @Inject
        ClassUnderTest classUnderTest;
    
        @Test
        public void test() {
            classUnderTest.run();
        }
    
        @MockBean(DependencyImpl.class)
        public Dependency dependency() {
            return mock(Dependency.class);
        }
    
    }
    

    此测试执行,dependency() 方法返回的模拟用于代替DependencyImpl

    使用@Replaces注解

    正如 cmets 部分中提到的 Sergio ,您可以使用 @Replaces 注释替换基于类的 bean 依赖项。考虑以下示例:

    package di.failure.example;
    
    import io.micronaut.context.annotation.Replaces;
    import io.micronaut.test.annotation.MicronautTest;
    import org.junit.jupiter.api.Test;
    
    import javax.inject.Inject;
    import javax.inject.Singleton;
    
    @MicronautTest
    public class ClassUnderTestTest {
    
        @Inject
        ClassUnderTest classUnderTest;
    
        @Test
        public void test() {
            classUnderTest.run();
        }
    
        @Replaces(Dependency.class)
        @Singleton
        public static class MockDependency extends Dependency {
    
            public MockDependency() {
                System.out.println("MockDependency.<init>");
            }
    
            @Override
            void run() {
                System.out.println("Does not throw any exception...");
            }
        }
    }
    

    在本例中,我们定义了一个类MockDependency,并指示Micronaut 的DI 机制将Dependency bean 替换为MockDependency。然而,我们需要记住一件重要的事情 - 因为我们的 MockDependency 扩展了 Dependency 类,父构造被调用。您在问题中显示的示例在这种情况下不起作用,因为 Dependency.&lt;init&gt; 抛出 RuntimeException 并且测试失败。在这个修改后的示例中,我使用了这样的类:

    package di.failure.example;
    
    import javax.inject.Singleton;
    
    @Singleton
    public class Dependency {
    
        public Dependency() {
            System.out.println("Dependency.<init>");
        }
    
        void run() {
            throw new RuntimeException("I don't want this to load!");
        }
    }
    

    当我运行测试时,它通过了,我看到以下控制台输出:

    Dependency.<init>
    MockDependency.<init>
    Does not throw any exception...
    

    @MockBean 相比的主要区别在于,在@Replaces 的情况下,您使用的是具体的类对象。作为一种解决方法(如果我们真的需要一个 Mockito 模拟对象)是在内部创建一个模拟并将调用委托给该对象,如下所示:

    @Replaces(Dependency.class)
    @Singleton
    public class MockDependency extends Dependency {
    
        private final Dependency delegate;
    
        public MockDependency() {
            this.delegate = mock(Dependency.class);
        }
    
        @Override
        void run() {
            delegate.run();
        }
    }
    

    【讨论】:

    • 好吧,真可惜(除非我有实际需要,否则我宁愿避免使用接口。)但我想这是一个。
    • 如果你需要替换一个类,你总是可以在测试类路径中创建一个bean并使用@Replaces
    • 感谢@SergiodelAmo 的宝贵意见!我已经用 @Replaces 注释用例更新了答案。
    • 我要覆盖什么属性值,而不是整个 bean?典型的例子是内存数据库。
    • “我想要覆盖什么属性值,而不是整个 bean?” - 您可以使用特定于环境的配置文件(例如 src/main/resources/application-test.yml)来做到这一点。标有@MicronautTest 的测试也可以提供自己的配置值。
    猜你喜欢
    • 2014-03-18
    • 1970-01-01
    • 2021-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-12
    相关资源
    最近更新 更多