Java 如何通过Mockito模拟无法访问的第三方类属性

Java 如何通过Mockito模拟无法访问的第三方类属性,java,junit,mocking,mockito,Java,Junit,Mocking,Mockito,我面前有一幅美丽的风景,包括JSF、jUnit(4.11)和Mockito(1.10.19): @ManagedBean @视域 公共类UserAuth实现可序列化{ 私人名单角色扮演者; 私有LocalChangeBean LocalChangeBean; 公共UserAuth(){ roleList=newarraylist(); localChangeBean=(localChangeBean)FacesContext.getCurrentInstance().getExternalCon

我面前有一幅美丽的风景,包括JSF、jUnit(4.11)和Mockito(1.10.19):

@ManagedBean
@视域
公共类UserAuth实现可序列化{
私人名单角色扮演者;
私有LocalChangeBean LocalChangeBean;
公共UserAuth(){
roleList=newarraylist();
localChangeBean=(localChangeBean)FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get(“localChangeBean”);
setLocalChangeBean(localChangeBean);
setRoleList(getLocalChangeBean().getRoleList());
//许多其他属性设置和一些JSF内容
}
公共布尔值checkAuth(){
for(UserRole角色:getRoleList()){
if(role.getName().equals(“超级用户”))
返回true;
}
返回false;
}
}
//更多的代码,合适的getter/setter等等。
以下是测试类:

public class UserAuthTest {

    @Test
    public void testCheckAuth() {

        UserAuth bean = mock(UserAuth.class);

        List<UserRole> mockRoleList = new ArrayList<UserRole>();
        UserRole ur = mock(UserRole.class);
        when(ur.getName()).thenReturn("SUPER_USER");
        mockRoleList.add(ur);

        when(bean.getRoleList()).thenReturn(mockRoleList);

        assertEquals(true, bean.checkAuth());
    }
公共类UserAuthTest{
@试验
公共void testCheckAuth(){
UserAuth bean=mock(UserAuth.class);
List mockRoleList=new ArrayList();
UserRole ur=mock(UserRole.class);
when(ur.getName())。然后返回(“超级用户”);
模拟角色列表。添加(ur);
when(bean.getRoleList())。然后return(mockRoleList);
assertEquals(true,bean.checkAuth());
}
问题是,我无法访问UserRole类,它是项目的另一部分。它没有无参数构造函数,并且现有构造函数需要其他无法访问的类等。因此我无法实例化它。在这种情况下,我只想让模拟UserRole对象表现为返回所需的String当调用getName()方法时

但是很明显,当我试图将UserRole模拟对象添加到UserRoles列表中时,我试图定义的行为并没有与该对象一起存储。是的,代码在当前的状态下看起来非常有趣。尽管我把它放在那里是为了了解我应该做些什么来实现我的这个简单的小目标

后期编辑: 我无法在不改变原始bean的情况下处理这个问题,尽管我遵循了Jeff的建议,并且它作为一种隔离策略很好地工作。我没有将它标记为最佳答案,因为问题是“如何模拟无法访问的第三方类?”(在当前示例中是UserRole类)最终,NoobMe明白了“嘲笑一个无法访问的第三方类与嘲笑任何其他类没有什么不同”

以下是我如何管理它的:

@ManagedBean
@ViewScoped
public class UserAuth implements Serializable {

 private List<UserRole> roleList;
 private LocalChangeBean localChangeBean;

public UserAuth() {

        //the actual constructor including all JSF logic, highly dependent
    }

UserAuth(List<UserRole> roleList) {
    setRoleList(roleList);
    //package private test-helper constructor which has no dependency on FacesContext etc.
  }
 public boolean checkAuth() {
        for (UserRole role : getRoleList()) {
            if(role.getName().equals("SUPER_USER"))
                return true;
        }
        return false;
    }
}
@ManagedBean
@视域
公共类UserAuth实现可序列化{
私人名单角色扮演者;
私有LocalChangeBean LocalChangeBean;
公共UserAuth(){
//实际构造函数包括所有JSF逻辑,高度依赖
}
UserAuth(列表角色列表){
赛特莱特(roleList);
//包私有测试助手构造函数,它不依赖于FacesContext等。
}
公共布尔值checkAuth(){
for(UserRole角色:getRoleList()){
if(role.getName().equals(“超级用户”))
返回true;
}
返回false;
}
}
下面是测试类(注意迭代器模拟,它有整个技巧):

公共类UserAuthTest{
私有用户角色mockRole;
私有迭代器角色迭代器;
私人名单模拟角色列表;
专用用户身份验证测试仪;
@抑制警告(“未选中”)
@以前
公共作废设置(){
mockRoleList=mock(List.class);
mockRole=mock(UserRole.class);
roleIterator=mock(迭代器类);
when(mockRoleList.iterator())。然后返回(roleIterator);
when(roleIterator.hasNext())。然后返回(true,false);
when(roleIterator.next())。然后return(mockRole);
tester=新用户身份验证(mockRoleList);
}
@试验
公共void testCheckAuth(){
when(mockRole.getName())。然后返回(“超级用户”);
assertEquals(“预期超级用户:”,true,tester.checkAuth());
}

您不需要Mockito。快速重构将为您做到这一点

您的问题:您的代码依赖于对构造函数中的
FacesContext.getCurrentInstance()
的静态调用,这很难在测试中准备或替换

您建议的解决方案:使用Mockito替换掉FacesContext实例、外部上下文或会话映射。这在一定程度上是很棘手的,因为Mockito是通过代理掉实例来工作的,因此如果没有PowerMock,您将无法替换静态调用,也无法将mock插入FacesContext或其tr中你别无选择

我建议的解决方案:打破错误调用
FacesContext.getCurrentInstance().getExternalContext.getSessionMap()
到默认构造函数中。不要从测试中调用该构造函数;假设它在单元测试用例中工作。相反,编写一个将会话映射作为
映射的构造函数,并从测试中调用该构造函数。这使您能够最好地测试自己的逻辑

@ManagedBean
@ViewScoped
public class UserAuth implements Serializable {
  // [snip]
  public UserAuth() {
    // For the public default constructor, use Faces and delegate to the
    // package-private constructor.
    this(FacesContext.getCurrentInstance().getExternalContext().getSessionMap());
  }

  /** Visible for testing. Allows passing in an arbitrary map. */
  UserAuth(Map<String, Object> sessionMap) {
    roleList = new ArrayList<UserRole>();

    localChangeBean = (LocalChangeBean) sessionMap.get("localChangeBean");

    setLocalChangeBean(localChangeBean);
    setRoleList(getLocalChangeBean().getRoleList());
    // [snip]
  }
}
@ManagedBean
@视域
公共类UserAuth实现可序列化{
//[剪报]
公共UserAuth(){
//对于公共默认构造函数,使用Faces并委托给
//包私有构造函数。
这是(FacesContext.getCurrentInstance().getExternalContext().getSessionMap());
}
/**测试可见。允许传入任意映射*/
UserAuth(映射会话映射){
roleList=newarraylist();
localChangeBean=(localChangeBean)sessionMap.get(“localChangeBean”);
setLocalChangeBean(localChangeBean);
setRoleList(getLocalChangeBean().getRoleList());
//[剪报]
}
}

p、 另一个解决方案是在测试中实际获取会话映射,并插入所需的值,但您必须小心,不要在测试之间将某些东西安装到可能持续存在的静态实例中,从而污染其他测试。

对于设计不完善的代码(并且不能
public class UserAuthTest {

private UserRole mockRole;
private Iterator<UserRole> roleIterator;
private List<UserRole> mockRoleList;

private UserAuth tester; 

@SuppressWarnings("unchecked")
@Before
public void setup() {

    mockRoleList = mock(List.class);
    mockRole = mock(UserRole.class);
    roleIterator = mock(Iterator.class);

    when(mockRoleList.iterator()).thenReturn(roleIterator);
    when(roleIterator.hasNext()).thenReturn(true, false);
    when(roleIterator.next()).thenReturn(mockRole);

    tester = new UserAuth(mockRoleList);

}

@Test
public void testCheckAuth(){
    when(mockRole.getName()).thenReturn("SUPER_USER");
    assertEquals("SUPER_USER expected: ", true, tester.checkAuth());
}
@ManagedBean
@ViewScoped
public class UserAuth implements Serializable {
  // [snip]
  public UserAuth() {
    // For the public default constructor, use Faces and delegate to the
    // package-private constructor.
    this(FacesContext.getCurrentInstance().getExternalContext().getSessionMap());
  }

  /** Visible for testing. Allows passing in an arbitrary map. */
  UserAuth(Map<String, Object> sessionMap) {
    roleList = new ArrayList<UserRole>();

    localChangeBean = (LocalChangeBean) sessionMap.get("localChangeBean");

    setLocalChangeBean(localChangeBean);
    setRoleList(getLocalChangeBean().getRoleList());
    // [snip]
  }
}