Mengganti Mengikat dalam Guice

138

Saya baru saja mulai bermain dengan Guice, dan kasus penggunaan yang dapat saya pikirkan adalah bahwa dalam ujian saya hanya ingin mengganti satu pengikatan. Saya pikir saya ingin menggunakan sisa ikatan tingkat produksi untuk memastikan semuanya sudah diatur dengan benar dan untuk menghindari duplikasi.

Jadi bayangkan saya memiliki Modul berikut

public class ProductionModule implements Module {
    public void configure(Binder binder) {
        binder.bind(InterfaceA.class).to(ConcreteA.class);
        binder.bind(InterfaceB.class).to(ConcreteB.class);
        binder.bind(InterfaceC.class).to(ConcreteC.class);
    }
}

Dan dalam pengujian saya, saya hanya ingin mengesampingkan InterfaceC, sambil tetap menjaga InterfaceA dan InterfaceB, jadi saya ingin sesuatu seperti:

Module testModule = new Module() {
    public void configure(Binder binder) {
        binder.bind(InterfaceC.class).to(MockC.class);
    }
};
Guice.createInjector(new ProductionModule(), testModule);

Saya juga sudah mencoba yang berikut ini, tanpa hasil:

Module testModule = new ProductionModule() {
    public void configure(Binder binder) {
        super.configure(binder);
        binder.bind(InterfaceC.class).to(MockC.class);
    }
};
Guice.createInjector(testModule);

Adakah yang tahu apakah itu mungkin untuk melakukan apa yang saya inginkan atau saya benar-benar menggonggong pohon yang salah ??

--- Tindak lanjut: Tampaknya saya dapat mencapai apa yang saya inginkan jika saya menggunakan tag @ImplementedBy pada antarmuka dan kemudian hanya memberikan pengikatan dalam test case, yang berfungsi dengan baik ketika ada 1-1 pemetaan antara antarmuka dan implementasi.

Juga, setelah mendiskusikan hal ini dengan seorang kolega, sepertinya kita akan menuju jalan menimpa seluruh modul dan memastikan kita memiliki modul kita didefinisikan dengan benar. Hal ini sepertinya dapat menyebabkan masalah meskipun di mana pengikatan salah tempat dalam modul dan perlu dipindahkan, sehingga mungkin memecah beban tes karena binding mungkin tidak lagi tersedia untuk diganti.

tddmonkey
sumber
7
Seperti ungkapan "menggonggong pohon yang salah": D
Boris Pavlovic

Jawaban:

149

Ini mungkin bukan jawaban yang Anda cari, tetapi jika Anda menulis tes unit, Anda mungkin tidak harus menggunakan injektor dan lebih baik menyuntikkan tiruan atau benda palsu dengan tangan.

Di sisi lain, jika Anda benar-benar ingin mengganti satu ikatan, Anda dapat menggunakan Modules.override(..):

public class ProductionModule implements Module {
    public void configure(Binder binder) {
        binder.bind(InterfaceA.class).to(ConcreteA.class);
        binder.bind(InterfaceB.class).to(ConcreteB.class);
        binder.bind(InterfaceC.class).to(ConcreteC.class);
    }
}
public class TestModule implements Module {
    public void configure(Binder binder) {
        binder.bind(InterfaceC.class).to(MockC.class);
    }
}
Guice.createInjector(Modules.override(new ProductionModule()).with(new TestModule()));

Lihat detailnya di sini .

Tetapi sebagai javadoc untuk Modules.overrides(..)merekomendasikan, Anda harus mendesain modul Anda sedemikian rupa sehingga Anda tidak perlu menimpa binding. Dalam contoh yang Anda berikan, Anda bisa mencapainya dengan memindahkan pengikatan InterfaceCke modul terpisah.

albertb
sumber
9
Terima kasih Albert, itu membuat saya di jalan menuju melakukan apa yang saya inginkan. Itu dalam rilis produksi belum tho! Dan ini untuk tes integrasi, bukan tes unit, yang saya mengapa saya ingin memastikan semuanya dibangun dengan benar
tddmonkey
1
Saya telah menambahkan contoh nyata ke kode. Apakah itu membuat Anda lebih lanjut?
albertb
1
Kecuali saya salah, ovveridekehilangan yang tepat Stagesaat melakukannya (yaitu PEMBANGUNAN digunakan secara sistematis).
pdeschen
4
Ukuran diperhitungkan. Ketika grafik ketergantungan Anda tumbuh, kabel dengan tangan bisa sangat menyebalkan. Juga ketika perubahan kabel Anda perlu memperbarui secara manual semua tempat kabel manual Anda. Overriding memungkinkan Anda untuk menanganinya secara otomatis.
yoosiba
3
@pdeschen Itu adalah bug di Guice 3 yang saya perbaiki untuk Guice 4.
Tavian Barnes
9

Mengapa tidak menggunakan warisan? Anda dapat mengganti ikatan spesifik Anda dalam overrideMemetode, meninggalkan implementasi bersama dalam configuremetode.

public class DevModule implements Module {
    public void configure(Binder binder) {
        binder.bind(InterfaceA.class).to(TestDevImplA.class);
        overrideMe(binder);
    }

    protected void overrideMe(Binder binder){
        binder.bind(InterfaceC.class).to(ConcreteC.class);
    }
};

public class TestModule extends DevModule {
    @Override
    public void overrideMe(Binder binder) {
        binder.bind(InterfaceC.class).to(MockC.class);
    }
}

Dan akhirnya buat injektor Anda dengan cara ini:

Guice.createInjector(new TestModule());
Mon Calamari
sumber
3
The @Overridetampaknya tidak bekerja. Apalagi jika itu dilakukan pada metode @Providessesuatu itu.
Sasanka Panguluri
4

Jika Anda tidak ingin mengubah modul produksi Anda dan jika Anda memiliki struktur proyek suka-suka standar seperti

src/test/java/...
src/main/java/...

Anda bisa membuat kelas baru ConcreteCdi direktori tes Anda menggunakan paket yang sama dengan kelas asli Anda. Guice maka akan mengikat InterfaceCke ConcreteCdari direktori tes sedangkan semua antarmuka lain akan terikat untuk kelas produksi Anda.

Jan Gassen
sumber
2

Anda ingin menggunakan Juckito di mana Anda dapat mendeklarasikan konfigurasi khusus Anda untuk setiap kelas uji.

@RunWith(JukitoRunner.class)
class LogicTest {
    public static class Module extends JukitoModule {

        @Override
        protected void configureTest() {
            bind(InterfaceC.class).to(MockC.class);
        }
    }

    @Inject
    private InterfaceC logic;

    @Test
    public testLogicUsingMock() {
        logic.foo();
    }
}
esukram
sumber
1

Dalam pengaturan yang berbeda, kami memiliki lebih dari satu aktivitas yang didefinisikan dalam modul terpisah. Aktivitas yang disuntikkan ke dalam Modul Perpustakaan Android, dengan definisi modul RoboGuice sendiri dalam file AndroidManifest.xml.

Setup terlihat seperti ini. Dalam Modul Perpustakaan ada definisi ini:

AndroidManifest.xml:

<application android:allowBackup="true">
    <activity android:name="com.example.SomeActivity/>
    <meta-data
        android:name="roboguice.modules"
        android:value="com.example.MainModule" />
</application>

Kemudian kami memiliki jenis yang disuntikkan:

interface Foo { }

Beberapa implementasi standar Foo:

class FooThing implements Foo { }

MainModule mengkonfigurasi implementasi FooThing untuk Foo:

public class MainModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(Foo.class).to(FooThing.class);
    }
}

Dan akhirnya, Aktivitas yang mengonsumsi Foo:

public class SomeActivity extends RoboActivity {
    @Inject
    private Foo foo;
}

Dalam Modul Aplikasi Android yang mengkonsumsi, kami ingin menggunakan SomeActivitytetapi, untuk tujuan pengujian, menyuntikkan kami sendiri Foo.

public class SomeOtherActivity extends Activity {
    @Override
    protected void onResume() {
        super.onResume();

        Intent intent = new Intent(this, SomeActivity.class);
        startActivity(intent);
    }
}

Orang mungkin berpendapat untuk mengekspos modul penanganan aplikasi klien, namun, kita perlu menyembunyikan sebagian besar komponen yang disuntikkan karena Modul Perpustakaan adalah SDK, dan mengekspos potongan memiliki implikasi yang lebih besar.

(Ingat, ini untuk pengujian, jadi kami tahu bagian dalam SomeActivity, dan tahu itu menghabiskan (paket terlihat) Foo).

Cara saya menemukan bahwa bekerja itu masuk akal; gunakan penggantian yang disarankan untuk pengujian :

public class SomeOtherActivity extends Activity {
    private class OverrideModule
            extends AbstractModule {

        @Override
        protected void configure() {
            bind(Foo.class).to(OtherFooThing.class);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RoboGuice.overrideApplicationInjector(
                getApplication(),
                RoboGuice.newDefaultRoboModule(getApplication()),
                Modules
                        .override(new MainModule())
                        .with(new OverrideModule()));
    }

    @Override
    protected void onResume() {
        super.onResume();

        Intent intent = new Intent(this, SomeActivity.class);
        startActivity(intent);
    }
}

Sekarang, ketika SomeActivitydimulai, ia akan mendapatkan OtherFooThinguntuk Foocontoh yang disuntikkan .

Ini adalah situasi yang sangat spesifik di mana, dalam kasus kami, OtherFooThing digunakan secara internal untuk merekam situasi pengujian, sementara FooThing digunakan, secara default, untuk semua penggunaan lainnya.

Perlu diingat, kita sedang menggunakan #newDefaultRoboModuledalam tes unit kami, dan bekerja sempurna.

Dave T.
sumber