如何模拟getApplicationContext

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

我正在为我的活动编写unit testing,我想检查当用户单击活动中的按钮时,应用程序状态将发生变化。 像这样的东西:

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

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

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

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

  • 你真的不能模拟一个正在测试的类,这是对象inheritance的许多缺点之一(即子类化)。
  • 另一个问题是ApplicationContext是一个单例 ,这使得测试更加邪恶,因为你不能轻易地模拟出一个被编程为不可替代的全局状态。

在这种情况下你可以做的是更喜欢对象组合而不是inheritance 。 因此,为了使您的Activity可测试,您需要将逻辑分开一点。 让我们说你的Activity被称为MyActivity 。 它需要逻辑组件(或类)组成,我们将其命名为MyActivityLogic 。 这是一个简单的类图图:

来自yUml的MyActivity和MyActivityLogic UML图

为了解决单例问题,我们让逻辑“注入”应用程序上下文,因此可以使用mock进行测试。 然后,我们只需要测试MyActivity对象是否已将正确的应用程序上下文放入MyActivityLogic 。 我们如何基本上解决这两个问题是通过另一层抽象 (从Butler Lampson转述)。 我们在这种情况下添加的新层是活动逻辑移动到活动对象之外。

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

 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... } 

它应该看起来像这样: 使用yUml中的方法创建的类

要测试MyActivityLogic您只需要一个简单的jUnit TestCase而不是ActivityUnitTestCase (因为它不是一个Activity),并且您可以使用您选择的模拟框架来模拟您的应用程序上下文(因为手动编辑您自己的模拟有点拖累)。 示例使用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(); } 

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

 // 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); 

希望这对你有意义并帮助你。