如何模拟getApplicationContext [英] How to mock getApplicationContext

查看:715
本文介绍了如何模拟getApplicationContext的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个存储应用上下文信息的应用程序。应用程序上下文信息在MyApp类中扩展Application类的活动之间共享。



我正在为我的活动编写单元测试,我想在用户点击时检查活动中的按钮,应用程序状态将发生变化。这样的事情:

  @Override 
public void onClick(查看pView){
((MyApp) getApplicationContext())setNewState();
}

问题是我不知道如何模拟该应用程序上下文。我使用 ActivityUnitTestCase 作为测试用例库。当我调用 setApplication 时,它会更改 Activity 类的 mApplication 成员的值,但不会更改应用程序上下文的值。我也尝试了 setActivityContext ,但它似乎不对(它不是应用程序上下文而是活动上下文)并且它在 startActivity 中触发断言。



所以问题是 - 如何模拟 getApplicationContext()

解决方案

由于方法 getApplicationContext 位于您正在扩展的类中,因此会出现问题。有几个问题需要考虑:




  • 你真的无法模拟一个正在测试的类,这是众多类中的一个对象继承的缺点(即子类化)。

  • 另一个问题是(从Butler Lampson转述)。我们在这种情况下添加的新层是活动逻辑移动到活动对象之外。



    为了您的示例,类需要看起来像这样:

     公共最终类MyActivityLogic {

    private MyApp mMyApp;

    public MyActivityLogic(MyApp pMyApp){
    mMyApp = pMyApp;
    }

    public MyApp getMyApp(){
    return mMyApp;
    }

    public void onClick(查看pView){
    getMyApp()。setNewState();
    }
    }

    公共最终类MyActivity扩展活动{

    //活动逻辑在mLogic
    私人最终MyActivityLogic mLogic;

    //在构造函数中创建逻辑
    public MyActivity(){
    super();
    mLogic = new MyActivityLogic(
    (MyApp)getApplicationContext());
    }

    // Getter,你也可以制作一个setter,但是我留下
    //这是你的练习
    public MyActivityLogic getMyActivityLogic(){
    返回mLogic;
    }

    //要测试的方法
    public void onClick(查看pView){
    mLogic.onClick(pView);
    }

    //肯定你在这里有其他代码...

    }

    它应该看起来像这样:



    要测试 MyActivityLogic ,您只需要一个简单的jUnit TestCase 而不是 ActivityUnitTestCase (因为它不是Activity),你可以使用你选择的模拟框架来模拟你的应用程序上下文(因为手动你自己的模拟有点拖累)。示例使用 Mockito

      MyActivityLogic mLogic; // CUT,被测组件
    MyApplication mMyApplication; //将被模拟

    protected void setUp(){
    //使用mockito创建模拟。
    mMyApplication = mock(MyApplication.class);
    //将模拟注入CUT
    mLogic = new MyActivityLogic(mMyApplication);
    }

    公共无效testOnClickShouldSetNewStateOnAppContext(){
    //测试由三个A
    的// ARRANGE:大多数的东西已在安装
    已经完成
    // ACT:通过调用逻辑
    mLogic.onClick(null)进行测试;

    // ASSERT:确保app.setNewState被称为
    verify(mMyApplication).setNewState();
    }

    测试 MyActivity 您像往常一样使用 ActivityUnitTestCase ,我们只需要确保它使用正确的<$ c创建 MyActivityLogic $ C>的ApplicationContext 。完成所有这些操作的粗略测试代码示例:

      // ARRANGE:
    MyActivity vMyActivity = getActivity();
    MyApp expectedAppContext = vMyActivity.getApplicationContext();

    // ACT:
    //没有必要,因为MyActivityLogic对象是在
    //构造活动
    MyActivityLogic VLOGIC = vMyActivity的创建 行动 多.getMyActivityLogic();

    // ASSERT:确保相同的ApplicationContext单例位于
    // MyActivityLogic对象内
    MyApp actualAppContext = vLogic.getMyApp();
    assertSame(expectedAppContext,actualAppContext);

    希望这对你有用并帮助你。


    I have an application that stores app context information. The app context information is shared between activities in MyApp class which extends Application class.

    I am writing a unit test for my activity, and I want to check that when user clicks a button in the activity, an application state will change. Something like this:

    @Override
    public void onClick(View pView) {
        ((MyApp)getApplicationContext()).setNewState();
    }   
    

    The problem is that I don't know how to mock that application context. I am using ActivityUnitTestCase as a test case base. When I call setApplication, it changes the value of mApplication member of Activity class, but not application context. I've tried setActivityContext also, but it seems wrong (it is not app context but activity context) and it fires assert inside startActivity).

    So the question is - how to mock getApplicationContext()?

    解决方案

    Since the method getApplicationContext is inside the class that you're extending it becomes somewhat problematic. There are a couple of problems to consider:

    • You really can't mock a class that is under test, which is one of the many drawbacks with object inheritance (i.e. subclassing).
    • The other problem is that ApplicationContext is a singleton, which makes it all more evil to test since you can't easily mock out a global state that is programmed to be irreplaceable.

    What you can do in this situation is to prefer object composition over inheritance. So in order to make your Activity testable you need to split up the logic a little. Lets say that your Activity is called MyActivity. It needs to be composed of a logic component (or class), lets name it MyActivityLogic. Here is a simple class-diagram figure:

    To solve the singleton problem, we let the logic be "injected" with an application context, so it can be tested with a mock. We then only need to test that the MyActivity object has put the correct application context into MyActivityLogic. How we basically solve both problems is through another layer of abstraction (paraphrased from Butler Lampson). The new layer we add in this case is the activity logic moved outside of the activity object.

    For the sake of your example the classes need to look sort-of like this:

    public final class MyActivityLogic {
    
        private MyApp mMyApp;
    
        public MyActivityLogic(MyApp pMyApp) {
            mMyApp = pMyApp;
        }
    
        public MyApp getMyApp() {
            return mMyApp;
        }
    
        public void onClick(View pView) {
            getMyApp().setNewState();
        }
    }
    
    public final class MyActivity extends Activity {
    
        // The activity logic is in mLogic
        private final MyActivityLogic mLogic;
    
        // Logic is created in constructor
        public MyActivity() {
            super(); 
            mLogic = new MyActivityLogic(
                (MyApp) getApplicationContext());
        }
    
        // Getter, you could make a setter as well, but I leave
        // that as an exercise for you
        public MyActivityLogic getMyActivityLogic() {
            return mLogic;
        }
    
        // The method to be tested
        public void onClick(View pView) {
            mLogic.onClick(pView);
        }
    
        // Surely you have other code here...
    
    }
    

    It should all look something like this:

    To test MyActivityLogic you will only need a simple jUnit TestCase instead of the ActivityUnitTestCase (since it isn't an Activity), and you can mock your application context using your mocking framework of choice (since handrolling your own mocks is a bit of a drag). Example uses Mockito:

    MyActivityLogic mLogic; // The CUT, Component Under Test
    MyApplication mMyApplication; // Will be mocked
    
    protected void setUp() {
        // Create the mock using mockito.
          mMyApplication = mock(MyApplication.class);
        // "Inject" the mock into the CUT
          mLogic = new MyActivityLogic(mMyApplication);
    }
    
    public void testOnClickShouldSetNewStateOnAppContext() {
        // Test composed of the three A's        
        // ARRANGE: Most stuff is already done in setUp
    
        // ACT: Do the test by calling the logic
        mLogic.onClick(null);
    
        // ASSERT: Make sure the application.setNewState is called
        verify(mMyApplication).setNewState();
    }
    

    To test the MyActivity you use ActivityUnitTestCase as usual, we only need to make sure that it creates a MyActivityLogic with the correct ApplicationContext. Sketchy test code example that does all this:

    // ARRANGE:
    MyActivity vMyActivity = getActivity();
    MyApp expectedAppContext = vMyActivity.getApplicationContext();
    
    // ACT: 
    // No need to "act" much since MyActivityLogic object is created in the 
    // constructor of the activity
    MyActivityLogic vLogic = vMyActivity.getMyActivityLogic();
    
    // ASSERT: Make sure the same ApplicationContext singleton is inside
    // the MyActivityLogic object
    MyApp actualAppContext = vLogic.getMyApp();
    assertSame(expectedAppContext, actualAppContext);
    

    Hope it all makes sense to you and helps you out.

    这篇关于如何模拟getApplicationContext的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆