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

时间:2013-05-07 18:44:12

标签: java spring unit-testing junit 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文件中维护测试用例的所有内容,而不是仅为我的测试维护单独的应用程序内容。

我希望将 Mockito 用于模拟对象。过去,我使用org.mockito.Mockito并使用Mockito.mock(MyClass.class)创建对象来完成此操作。

6 个答案:

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

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

  1. 您不需要手动注入myService。
  2. 您不需要使用Mockito运行器或规则。

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