简体   繁体   English

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

[英]How to mock unreachable third party class properties via Mockito

I have this beautiful scenery in front of me including JSF, jUnit(4.11) and Mockito(1.10.19): 我眼前有这个美丽的风景,包括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.

Here is the test class: 这是测试类:

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

The thing is; 事情是; UserRole class is not reachable by me, it's another part of the project. 我无法访问UserRole类,这是项目的另一部分。 It doesn't have a no-argument constructor and the existing constructor requires other unreachable classes etc. Thus I can't instantiate it. 它没有无参数构造函数,而现有的构造函数需要其他无法访问的类等。因此,我无法实例化它。 In these circumstances, all I want to do is to make that mock UserRole object behave such as returning the needed String when it's getName() method gets called. 在这种情况下,我要做的就是使模拟的UserRole对象表现出来,例如在调用getName()方法时返回所需的String。

But obviously; 但是很明显; when I try to add that UserRole mock object into the List of UserRoles, the behavior that I tried to define is not stored with the object. 当我尝试将UserRole模拟对象添加到UserRoles列表中时,我尝试定义的行为未与该对象一起存储。 And yes, the code looks pretty funny in its current stance. 是的,该代码在当前的状态下看起来很有趣。 Though I left it there to learn what should I do to achieve this simple, little goal of mine. 尽管我把它留在了那里,但要学习实现我这个简单的小目标应该怎么做。

Post-Edit: I couldn't manage the problem without changing the original bean, though I followed Jeff's suggestion below and it worked well as a strategy of isolation. 编辑后:我不改变原始bean就无法解决问题,尽管我遵循了Jeff的建议,并且它可以很好地用作隔离策略。 I did not mark it as the best answer since the question was "How to mock an unreachable third party class?" 我没有将其标记为最佳答案,因为问题是“如何嘲笑无法到达的第三方阶层?” (in the current example its the UserRole class) Eventually the noob me understood that "Mocking an unreachable third party class is no different than mocking any other class". (在当前示例中,它是UserRole类)最终,我理解了“模拟一个无法访问的第三方类与模拟任何其他类没有什么不同”。

Here is how I managed it: 这是我的管理方式:

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

And here is the test class (attention to the iterator mock, it has the whole trick): 这是测试类(注意迭代器模拟,它具有整个技巧):

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

You don't need Mockito. 您不需要Mockito。 A quick refactor will do this for you. 快速重构将为您完成此任务。

Your problem: Your code relies on a static call to FacesContext.getCurrentInstance() in your constructor, that is difficult to prepare or substitute out in tests. 您的问题:您的代码依赖于构造函数中对FacesContext.getCurrentInstance()的静态调用,这很难在测试中准备或替换。

Your proposed solution: Use Mockito to substitute out the FacesContext instance, the external context, or the session map. 您建议的解决方案:使用Mockito替代FacesContext实例,外部上下文或会话映射。 This is partly tricky because Mockito works by proxying out the instances, so without PowerMock you won't be able to replace the static call, and without a way to insert the mock into FacesContext or its tree, you have no alternative. 这在某种程度上是棘手的,因为Mockito通过代理实例来工作,因此,如果没有PowerMock,您将无法替换静态调用,并且没有办法将模拟插入FacesContext或其树,则别无选择。

My proposed solution: Break out the bad call FacesContext.getCurrentInstance().getExternalContext.getSessionMap() into the default constructor. 我建议的解决方案:将对FacesContext.getCurrentInstance().getExternalContext.getSessionMap()的错误调用分解为默认构造函数。 Don't call that constructor from tests; 不要从测试中调用该构造函数。 assume it works in the unit testing case. 假设它可以在单元测试用例中使用。 Instead, write a constructor that takes in the session map as a Map<String, Object> , and call that constructor from your tests. 而是编写一个将会话映射作为Map<String, Object>构造函数,然后从测试中调用该构造函数。 That gives you the best ability to test your own logic. 这使您能够最好地测试自己的逻辑。

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

ps Another solution is to actually get the session map within the test and insert the value you need, but you have to be careful there not to pollute your other tests by installing something into a static instance that may persist between tests. ps另一个解决方案是在测试中实际获取会话映射并插入所需的值,但是您必须注意不要通过在静态实例中安装某些东西而污染其他测试,这些实例可能会在测试之间持续存在。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM