65

単一の依存関係を持つ単純な Android アクティビティがあります。onCreate次のように、アクティビティに依存関係を挿入します。

Dagger_HelloComponent.builder()
    .helloModule(new HelloModule(this))
    .build()
    .initialize(this);

ActivityUnitTestCaseの場合、依存関係を Mockito モックでオーバーライドしたいと考えています。モックを提供するテスト固有のモジュールを使用する必要があると思いますが、このモジュールをオブジェクト グラフに追加する方法がわかりません。

Dagger 1.x では、これは明らかに次のような方法で行われます。

@Before
public void setUp() {
  ObjectGraph.create(new TestModule()).inject(this);
}

上記に相当する Dagger 2.0 は何ですか?

私のプロジェクトとその単体テストは GitHub で見ることができます。

4

9 に答える 9

25

@EpicPandaForce が正しく言うように、モジュールを拡張することはできません。しかし、私はこれに対する卑劣な回避策を思いつきました。これは、他の例が苦しんでいるボイラープレートの多くを回避すると思います.

モジュールを「拡張」する秘訣は、部分的なモックを作成し、オーバーライドしたいプロバイダー メソッドをモックアウトすることです。

モッキートの使用:

MyModule module = Mockito.spy(new MyModule());
Mockito.doReturn("mocked string").when(module).provideString();

MyComponent component = DaggerMyComponent.builder()
        .myModule(module)
        .build();

app.setComponent(component);

完全な例を示すために、この要点をここに作成しました。

編集

次のように、部分的なモックがなくてもこれを実行できることがわかりました。

MyComponent component = DaggerMyComponent.builder()
        .myModule(new MyModule() {
            @Override public String provideString() {
                return "mocked string";
            }
        })
        .build();

app.setComponent(component);
于 2015-08-27T15:12:51.983 に答える
2

私はさらに別の方法を見つけたようで、これまでのところ機能しています。

まず、コンポーネント自体ではないコンポーネント インターフェイス:

MyComponent.java

interface MyComponent {
    Foo provideFoo();
}

次に、実際のモジュールとテスト用の 2 つの異なるモジュールがあります。

MyModule.java

@Module
class MyModule {
    @Provides
    public Foo getFoo() {
        return new Foo();
    }
}

TestModule.java

@Module
class TestModule {
    private Foo foo;
    public void setFoo(Foo foo) {
        this.foo = foo;
    }

    @Provides
    public Foo getFoo() {
        return foo;
    }
}

そして、これら 2 つのモジュールを使用するための 2 つのコンポーネントがあります。

MyRealComponent.java

@Component(modules=MyModule.class)
interface MyRealComponent extends MyComponent {
    Foo provideFoo(); // without this dagger will not do its magic
}

MyTestComponent.java

@Component(modules=TestModule.class)
interface MyTestComponent extends MyComponent {
    Foo provideFoo();
}

アプリケーションでは、次のようにします。

MyComponent component = DaggerMyRealComponent.create();
<...>
Foo foo = component.getFoo();

テストコードでは、次を使用します。

TestModule testModule = new TestModule();
testModule.setFoo(someMockFoo);
MyComponent component = DaggerMyTestComponent.builder()
    .testModule(testModule).build();
<...>
Foo foo = component.getFoo(); // will return someMockFoo

問題は、MyModule のすべてのメソッドを TestModule にコピーする必要があることですが、外部から直接設定しない限り、TestModule 内に MyModule を配置し、MyModule のメソッドを使用することで実現できます。このような:

TestModule.java

@Module
class TestModule {
    MyModule myModule = new MyModule();
    private Foo foo = myModule.getFoo();
    public void setFoo(Foo foo) {
        this.foo = foo;
    }

    @Provides
    public Foo getFoo() {
        return foo;
    }
}
于 2016-02-02T17:22:30.943 に答える
0

私のソリューションをチェックしていただけますか。サブコンポーネントの例を含めました: https://github.com/nongdenchet/android-mvvm-with-tests。@vaughandroid ありがとうございます。オーバーライド メソッドをお借りしました。主なポイントは次のとおりです。

  1. サブコンポーネントを作成するクラスを作成します。私のカスタム アプリケーションも、このクラスのインスタンスを保持します。

    // The builder class
    public class ComponentBuilder {
     private AppComponent appComponent;
    
     public ComponentBuilder(AppComponent appComponent) {
      this.appComponent = appComponent;
     }
    
     public PlacesComponent placesComponent() {
      return appComponent.plus(new PlacesModule());
     }
    
     public PurchaseComponent purchaseComponent() {
      return appComponent.plus(new PurchaseModule());
     }
    }
    
    // My custom application class
    public class MyApplication extends Application {
    
     protected AppComponent mAppComponent;
     protected ComponentBuilder mComponentBuilder;
    
     @Override
     public void onCreate() {
      super.onCreate();
    
      // Create app component
      mAppComponent = DaggerAppComponent.builder()
              .appModule(new AppModule())
              .build();
    
      // Create component builder
      mComponentBuilder = new ComponentBuilder(mAppComponent);
     }
    
     public AppComponent component() {
      return mAppComponent;
     }
    
     public ComponentBuilder builder() {
      return mComponentBuilder;
     } 
    }
    
    // Sample using builder class:
    public class PurchaseActivity extends BaseActivity {
     ...    
     @Override
     protected void onCreate(Bundle savedInstanceState) {
      ...
      // Setup dependency
      ((MyApplication) getApplication())
              .builder()
              .purchaseComponent()
              .inject(this);
      ...
     }
    }
    
  2. 上記の MyApplication クラスを拡張するカスタム TestApplication があります。このクラスには、ルート コンポーネントとビルダーを置き換える 2 つのメソッドが含まれています。

    public class TestApplication extends MyApplication {
     public void setComponent(AppComponent appComponent) {
      this.mAppComponent = appComponent;
     }
    
     public void setComponentBuilder(ComponentBuilder componentBuilder) {
      this.mComponentBuilder = componentBuilder;
     }
    }    
    
  3. 最後に、モジュールとビルダーの依存関係をモックまたはスタブして、アクティビティに偽の依存関係を提供しようとします。

    @MediumTest
    @RunWith(AndroidJUnit4.class)
    public class PurchaseActivityTest {
    
     @Rule
     public ActivityTestRule<PurchaseActivity> activityTestRule =
         new ActivityTestRule<>(PurchaseActivity.class, true, false);
    
     @Before
     public void setUp() throws Exception {
     PurchaseModule stubModule = new PurchaseModule() {
         @Provides
         @ViewScope
         public IPurchaseViewModel providePurchaseViewModel(IPurchaseApi purchaseApi) {
             return new StubPurchaseViewModel();
         }
     };
    
     // Setup test component
     AppComponent component = ApplicationUtils.application().component();
     ApplicationUtils.application().setComponentBuilder(new ComponentBuilder(component) {
         @Override
         public PurchaseComponent purchaseComponent() {
             return component.plus(stubModule);
         }
     });
    
     // Run the activity
     activityTestRule.launchActivity(new Intent());
    }
    
于 2015-10-25T09:44:16.637 に答える
-5

Dagger2 では、生成されたビルダー API を使用して、特定のモジュール (そこにある TestModule) をコンポーネントに渡すことができます。

ApplicationComponent appComponent = Dagger_ApplicationComponent.builder()
                .helloModule(new TestModule())
                .build();

Dagger_ApplicationComponent は、新しい @Component アノテーションを使用して生成されたクラスであることに注意してください。

于 2014-11-15T12:04:19.960 に答える