89

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

@Component
public class MyLauncher {
    @Autowired
    MyService myService;

    //other methods
}

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

我想为 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)

4

6 回答 6

92

您绝对可以在测试中在 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() {

    }
}
于 2013-05-07T18:55:43.343 回答
70

接受的答案(使用MockitoJUnitRunnerand @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);
        }
    }
于 2016-09-16T08:17:04.703 回答
20

有时您可以重构您@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() {

    }
}

如果测试和被测类位于同一个包中,此技术特别有用,因为您可以使用默认的包私有访问修饰符来防止其他类访问它。请注意,您仍然可以将生产代码放在目录中,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 运行器并在方法中调用MockitoAnnotations.initMocks()setUp()以让 Mockito 初始化带注释的值。您可以在@InjectMocks的 javadoc和我写的博客文章中找到更多信息。

于 2013-05-07T19:30:47.220 回答
3

我相信为了在您的 MyLauncher 类(对于 myService)上进行自动装配工作,您需要让 Spring 通过自动装配 myLauncher 来初始化它而不是调用构造函数。一旦它被自动连接(并且 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
}

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

  1. 您不需要手动注入 myService。
  2. 您不需要使用 Mockito 跑步者或规则。
于 2018-08-27T14:08:49.397 回答
2

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

这是我的身份验证控制器,它有一些 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 :)

于 2017-03-06T07:44:15.377 回答
1

看看这个链接

然后将您的测试用例编写为

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

@Resource
private MyLauncher myLauncher ;

   @Test
   public void someTest() {
       //test code
   }
}
于 2013-05-07T18:55:18.213 回答