我有一个组件设置,它本质上是一个应用程序的启动器。它配置如下:
@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
文件中维护测试用例的所有内容,而不是仅为我的测试维护单独的应用程序内容。
我希望将 Mockito 用于模拟对象。过去,我使用org.mockito.Mockito
并使用Mockito.mock(MyClass.class)
创建对象来完成此操作。
答案 0 :(得分:66)
在测试中,你绝对可以在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() {
}
}
答案 1 :(得分:32)
接受的答案(使用MockitoJUnitRunner
和@InjectMocks
)非常棒。但是,如果你想要一些更轻量级的东西(没有特别的JUnit跑步者),那么更少"魔法" (更透明),特别是偶尔使用,你可以直接使用内省设置私有字段。
如果你使用Spring,你已经拥有了一个实用工具类:org.springframework.test.util.ReflectionTestUtils
使用非常简单:
ReflectionTestUtils.setField(myLauncher, "myService", myService);
第一个参数是你的目标bean,第二个是(通常是私有)字段的名称,最后一个是要注入的值。
如果你不使用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);
}
}
答案 2 :(得分:14)
有时你可以重构你的@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() {
}
}
如果测试和被测试的类位于同一个包中,这项技术特别有用,因为您可以使用默认的package-private访问修饰符来阻止其他类访问它。请注意,您仍然可以在src/main/java
中使用生产代码,但在src/main/test
目录中进行测试。
如果您喜欢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() {
}
}
或者,您可以使用默认的JUnit运行器并使用setUp()
方法调用MockitoAnnotations.initMocks(),让Mockito初始化带注释的值。您可以在@InitMocks的javadoc和我编写的blog post中找到更多信息。
答案 3 :(得分:1)
看看这个link
然后将您的测试用例编写为
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/applicationContext.xml"})
public class MyLauncherTest{
@Resource
private MyLauncher myLauncher ;
@Test
public void someTest() {
//test code
}
}
答案 4 :(得分:1)
我相信,为了在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();
}
}
然后,您的MyLauncher类可以保持不变,并且MyService bean将是一个模拟,其方法将返回您定义的值:
@Component
public class MyLauncher {
@Autowired
MyService myService;
public void doSomething() {
myService.someVoidMethod();
myService.someMethodThatCallsSomeStringMethod();
}
//other methods
}
与其他提到的方法相比,此方法有两个优点:
答案 5 :(得分:0)
我是Spring的新用户。我找到了一个不同的解决方案。使用反射并制作公共必要字段并分配模拟对象。
这是我的auth控制器,它有一些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;
}
}
这是我对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);
}
}
}
我不知道这种方式的优点和缺点,但这是有效的。这个技术有一些代码,但这些代码可以通过不同的方法分离等。这个问题有更好的答案,但我想指出不同的解决方案。对不起,我的英语不好。给大家一个好的java :))