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

时间:2016-05-09 14:48:59

标签: java junit mocking mockito

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

@ManagedBean
@ViewScoped
public class UserAuth implements Serializable {

 private List<UserRole> roleList;
 private LocalChangeBean localChangeBean;

public UserAuth() {

        roleList = new ArrayList<UserRole>();

        localChangeBean = (LocalChangeBean) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("localChangeBean");

        setLocalChangeBean(localChangeBean);

        setRoleList(getLocalChangeBean().getRoleList());
        //many other property setting and some JSF stuff
    }

 public boolean checkAuth() {
        for (UserRole role : getRoleList()) {
            if(role.getName().equals("SUPER_USER"))
                return true;
        }
        return false;
    }
}

//A hell of a lot more code, proper getters/setters etc.

这是测试类:

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

事情是;我无法访问UserRole类,它是项目的另一部分。它没有无参数构造函数,现有构造函数需要其他无法访问的类等。因此我无法实例化它。在这些情况下,我想要做的就是使模拟UserRole对象的行为,例如在调用getName()方法时返回所需的String。

但显然;当我尝试将UserRole模拟对象添加到UserRoles列表中时,我尝试定义的行为不会与对象一起存储。是的,代码在当前的立场看起来很有趣。虽然我把它留在那里,以了解我应该怎样做才能实现我的这个简单的小目标。

后编辑: 我不能在不改变原始bean的情况下解决这个问题,尽管我遵循了Jeff的建议,但它作为一种隔离策略效果很好。我没有把它标记为最佳答案,因为问题是“如何模拟一个无法到达的第三方课程?” (在当前的例子中它是UserRole类)最后,菜鸟我理解“模拟一个无法到达的第三方类与嘲弄任何其他类没有什么不同”。

以下是我管理它的方式:

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

这是测试类(注意迭代器模拟,它有完整的技巧):

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

1 个答案:

答案 0 :(得分:2)

你不需要Mockito。快速重构将为您完成此任务。

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

您提出的解决方案:使用Mockito替换FacesContext实例,外部上下文或会话映射。这有点棘手,因为Mockito通过代理实例来工作,所以如果没有PowerMock,你将无法替换静态调用,并且没有办法将模拟插入FacesContext或其树,你别无选择。

我建议的解决方案:将错误调用FacesContext.getCurrentInstance().getExternalContext.getSessionMap()分解为默认构造函数。不要从测试中调用该构造函数;假设它在单元测试用例中有效。相反,编写一个构造函数,将会话映射作为Map<String, Object>,并从测试中调用该构造函数。这为您提供了测试自己逻辑的最佳能力。

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

P.S。另一种解决方案是在测试中实际获取会话映射并插入您需要的值,但是您必须小心,不要通过在可能在测试之间持续存在的静态实例中安装内容来污染您的其他测试。