Java 在测试期间注入@Autowired 私有字段

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16426323/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-16 06:52:25  来源:igfitidea点击:

Injecting @Autowired private field during testing

javaspringunit-testingjunitautowired

提问by Kyle

I have a component setup that is essentially a launcher for an application. It is configured like so:

我有一个组件设置,它本质上是一个应用程序的启动器。它是这样配置的:

@Component
public class MyLauncher {
    @Autowired
    MyService myService;

    //other methods
}

MyService is annotated with the @ServiceSpring annotation and is autowired into my launcher class without any issues.

MyService 用@ServiceSpring 注释进行了注释,并自动连接到我的启动器类中,没有任何问题。

I would like to write some jUnit test cases for MyLauncher, to do so I started a class like this:

我想为 MyLauncher 编写一些 jUnit 测试用例,为此我开始了一个这样的类:

public class MyLauncherTest
    private MyLauncher myLauncher = new MyLauncher();

    @Test
    public void someTest() {

    }
}

Can I create a Mock object for MyService and inject it into myLauncher in my test class? I currently don't have a getter or setter in myLauncher as Spring is handling the autowiring. If possible, I'd like to not have to add getters and setters. Can I tell the test case to inject a mock object into the autowired variable using an @Beforeinit method?

我可以为 MyService 创建一个 Mock 对象并将它注入到我的测试类中的 myLauncher 中吗?我目前在 myLauncher 中没有 getter 或 setter,因为 Spring 正在处理自动装配。如果可能,我不想添加 getter 和 setter。我可以告诉测试用例使用@Beforeinit 方法将模拟对象注入到自动装配的变量中吗?

If I'm going about this completely wrong, feel free to say that. I'm still new to this. My main goal is to just have some Java code or annotation that puts a mock object in that @Autowiredvariable without me having to write a setter method or having to use an applicationContext-test.xmlfile. I would much rather maintain everything for the test cases in the .javafile instead of having to maintain a separate application content just for my tests.

如果我完全错了,请随意说出来。我还是新手。我的主要目标是只需要一些 Java 代码或注释,将模拟对象放入该@Autowired变量中,而不必编写 setter 方法或使用applicationContext-test.xml文件。我宁愿为.java文件中的测试用例维护所有内容,而不必为我的测试维护单独的应用程序内容。

I am hoping to use Mockitofor the mock objects. In the past I have done this by using org.mockito.Mockitoand creating my objects with Mockito.mock(MyClass.class).

我希望将Mockito用于模拟对象。在过去,我通过使用org.mockito.Mockito和创建我的对象来做到这一点Mockito.mock(MyClass.class)

采纳答案by Manuel Quinones

You can absolutely inject mocks on MyLauncher in your test. I am sure if you show what mocking framework you are using someone would be quick to provide an answer. With mockito I would look into using @RunWith(MockitoJUnitRunner.class) and using annotations for myLauncher. It would look something like what is below.

您绝对可以在测试中在 MyLauncher 上注入模拟。我相信如果您展示您使用的模拟框架,某人会很快提供答案。使用 mockito,我会考虑使用 @RunWith(MockitoJUnitRunner.class) 并为 myLauncher 使用注释。它看起来像下面的内容。

@RunWith(MockitoJUnitRunner.class)
public class MyLauncherTest
    @InjectMocks
    private MyLauncher myLauncher = new MyLauncher();

    @Mock
    private MyService myService;

    @Test
    public void someTest() {

    }
}

回答by NullPointerException

Look at this link

看这个链接

Then write your test case as

然后将您的测试用例写为

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/applicationContext.xml"})
public class MyLauncherTest{

@Resource
private MyLauncher myLauncher ;

   @Test
   public void someTest() {
       //test code
   }
}

回答by matsev

Sometimes you can refactor your @Componentto use constructor or setter based injection to setup your testcase (you can and still rely on @Autowired). Now, you can create your test entirely without a mocking framework by implementing test stubs instead (e.g. Martin Fowler's MailServiceStub):

有时您可以重构您@Component的使用构造函数或基于 setter 的注入来设置您的测试用例(您可以并且仍然依赖于@Autowired)。现在,您可以通过实现测试存根(例如 Martin Fowler 的MailServiceStub)来完全创建您的测试,而无需模拟框架:

@Component
public class MyLauncher {

    private MyService myService;

    @Autowired
    MyLauncher(MyService myService) {
        this.myService = myService;
    }

    // other methods
}

public class MyServiceStub implements MyService {
    // ...
}

public class MyLauncherTest
    private MyLauncher myLauncher;
    private MyServiceStub myServiceStub;

    @Before
    public void setUp() {
        myServiceStub = new MyServiceStub();
        myLauncher = new MyLauncher(myServiceStub);
    }

    @Test
    public void someTest() {

    }
}

This technique especially useful if the test and the class under test is located in the same package because then you can use the default, package-privateaccess modifier to prevent other classes from accessing it. Note that you can still have your production code in src/main/javabut your tests in src/main/testdirectories.

如果测试和被测类位于同一个包中,这种技术特别有用,因为这样您就可以使用默认的包私有访问修饰符来防止其他类访问它。请注意,您仍然可以将生产代码src/main/java放入src/main/test目录中,但将测试放入目录中。



If you like Mockito then you will appreciate the MockitoJUnitRunner. It allows you to do "magic" things like @Manuel showed you:

如果您喜欢 Mockito,那么您将欣赏MockitoJUnitRunner。它允许你做“神奇”的事情,比如@Manuel 向你展示的:

@RunWith(MockitoJUnitRunner.class)
public class MyLauncherTest
    @InjectMocks
    private MyLauncher myLauncher; // no need to call the constructor

    @Mock
    private MyService myService;

    @Test
    public void someTest() {

    }
}

Alternatively, you can use the default JUnit runner and call the MockitoAnnotations.initMocks()in a setUp()method to let Mockito initialize the annotated values. You can find more information in the javadoc of @InitMocksand in a blog postthat I have written.

或者,您可以使用默认的 JUnit 运行程序并在方法中调用MockitoAnnotations.initMocks()setUp()以让 Mockito 初始化带注释的值。您可以在@InitMocks的 javadoc和我写的博客文章中找到更多信息。

回答by Pierre Henry

The accepted answer (use MockitoJUnitRunnerand @InjectMocks) is great. But if you want something a little more lightweight (no special JUnit runner), and less "magical" (more transparent) especially for occasional use, you could just set the private fields directly using introspection.

接受的答案(使用MockitoJUnitRunner@InjectMocks)很棒。但是如果你想要一些更轻量的东西(没有特殊的 JUnit runner),并且不那么“神奇”(更透明),尤其是偶尔使用,你可以直接使用自省设置私有字段。

If you use Spring, you already have a utility class for this : org.springframework.test.util.ReflectionTestUtils

如果您使用 Spring,那么您已经有一个用于此的实用程序类: org.springframework.test.util.ReflectionTestUtils

The use is quite straightforward :

使用非常简单:

ReflectionTestUtils.setField(myLauncher, "myService", myService);

The first argument is your target bean, the second is the name of the (usually private) field, and the last is the value to inject.

第一个参数是你的目标 bean,第二个是(通常是私有的)字段的名称,最后一个是要注入的值。

If you don't use Spring, it is quite trivial to implement such a utility method. Here is the code I used before I found this Spring class :

如果不使用Spring,实现这样的实用方法就很简单了。这是我在找到这个 Spring 类之前使用的代码:

public static void setPrivateField(Object target, String fieldName, Object value){
        try{
            Field privateField = target.getClass().getDeclaredField(fieldName);
            privateField.setAccessible(true);
            privateField.set(target, value);
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }

回答by kodmanyagha

I'm a new user for Spring. I found a different solution for this. Using reflection and making public necessary fields and assign mock objects.

我是 Spring 的新用户。我为此找到了不同的解决方案。使用反射并公开必要的字段并分配模拟对象。

This is my auth controller and it has some Autowired private properties.

这是我的身份验证控制器,它有一些 Autowired 私有属性。

@RestController
public class AuthController {

    @Autowired
    private UsersDAOInterface usersDao;

    @Autowired
    private TokensDAOInterface tokensDao;

    @RequestMapping(path = "/auth/getToken", method = RequestMethod.POST)
    public @ResponseBody Object getToken(@RequestParam String username,
            @RequestParam String password) {
        User user = usersDao.getLoginUser(username, password);

        if (user == null)
            return new ErrorResult("Kullan?c?ad? veya ?ifre hatal?");

        Token token = new Token();
        token.setTokenId("aergaerg");
        token.setUserId(1);
        token.setInsertDatetime(new Date());
        return token;
    }
}

And this is my Junit test for AuthController. I'm making public needed private properties and assign mock objects to them and rock :)

这是我对 AuthController 的 Junit 测试。我正在公开需要的私有属性并将模拟对象分配给它们并摇滚:)

public class AuthControllerTest {

    @Test
    public void getToken() {
        try {
            UsersDAO mockUsersDao = mock(UsersDAO.class);
            TokensDAO mockTokensDao = mock(TokensDAO.class);

            User dummyUser = new User();
            dummyUser.setId(10);
            dummyUser.setUsername("nixarsoft");
            dummyUser.setTopId(0);

            when(mockUsersDao.getLoginUser(Matchers.anyString(), Matchers.anyString())) //
                    .thenReturn(dummyUser);

            AuthController ctrl = new AuthController();

            Field usersDaoField = ctrl.getClass().getDeclaredField("usersDao");
            usersDaoField.setAccessible(true);
            usersDaoField.set(ctrl, mockUsersDao);

            Field tokensDaoField = ctrl.getClass().getDeclaredField("tokensDao");
            tokensDaoField.setAccessible(true);
            tokensDaoField.set(ctrl, mockTokensDao);

            Token t = (Token) ctrl.getToken("test", "aergaeg");

            Assert.assertNotNull(t);

        } catch (Exception ex) {
            System.out.println(ex);
        }
    }

}

I don't know advantages and disadvantages for this way but this is working. This technic has a little bit more code but these codes can be seperated by different methods etc. There are more good answers for this question but I want to point to different solution. Sorry for my bad english. Have a good java to everybody :)

我不知道这种方式的优点和缺点,但这是有效的。这个技术有更多的代码,但这些代码可以通过不同的方法等分开。这个问题有更多好的答案,但我想指出不同的解决方案。对不起,我的英语不好。祝大家有个好的java :)

回答by snydergd

I believe in order to have auto-wiring work on your MyLauncher class (for myService), you will need to let Spring initialize it instead of calling the constructor, by auto-wiring myLauncher. Once that is being auto-wired (and myService is also getting auto-wired), Spring (1.4.0 and up) provides a @MockBean annotation you can put in your test. This will replace a matching single beans in context with a mock of that type. You can then further define what mocking you want, in a @Before method.

我相信为了在您的 MyLauncher 类(用于 myService)上进行自动连接工作,您需要通过自动连接 myLauncher 来让 Spring 初始化它而不是调用构造函数。一旦它被自动连接(并且 myService 也被自动连接),Spring(1.4.0 及更高版本)提供了一个 @MockBean 注释,您可以将其放入您的测试中。这将用该类型的模拟替换上下文中匹配的单个 bean。然后,您可以在 @Before 方法中进一步定义您想要的模拟。

public class MyLauncherTest
    @MockBean
    private MyService myService;

    @Autowired
    private MyLauncher myLauncher;

    @Before
    private void setupMockBean() {
        doNothing().when(myService).someVoidMethod();
        doReturn("Some Value").when(myService).someStringMethod();
    }

    @Test
    public void someTest() {
        myLauncher.doSomething();
    }
}

Your MyLauncher class can then remain unmodified, and your MyService bean will be a mock whose methods return values as you defined:

然后,您的 MyLauncher 类可以保持不变,而您的 MyService bean 将是一个模拟,其方法返回您定义的值:

@Component
public class MyLauncher {
    @Autowired
    MyService myService;

    public void doSomething() {
        myService.someVoidMethod();
        myService.someMethodThatCallsSomeStringMethod();
    }

    //other methods
}

A couple advantages of this over other methods mentioned is that:

与提到的其他方法相比,它的几个优点是:

  1. You don't need to manually inject myService.
  2. You don't need use the Mockito runner or rules.
  1. 您不需要手动注入 myService。
  2. 您不需要使用 Mockito 跑步者或规则。