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

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

@Component
public class MyLauncher {
@Autowired
MyService myService;


//other methods
}

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

我想为 MyLauncher 编写一些 jUnit 测试用例,为此我启动了一个类:

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


@Test
public void someTest() {


}
}

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

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

我希望对模拟对象使用 莫基托。在过去,我通过使用 org.mockito.Mockito和使用 Mockito.mock(MyClass.class)创建我的对象来实现这一点。

170129 次浏览

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
}
}

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.

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


@Mock
private MyService myService;


@Test
public void someTest() {


}
}

Sometimes you can refactor your @Component to 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
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-private access modifier to prevent other classes from accessing it. Note that you can still have your production code in src/main/java but your tests in src/main/test directories.


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

@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 @InjectMocks and in a blog post that I have written.

The accepted answer (use MockitoJUnitRunner and @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.

If you use Spring, you already have a utility class for this : 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.

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 :

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);
}
}

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.

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

@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 :)

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 :)

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.

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:

@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.