Guice'de Geçersiz Kılma


138

Guice ile oynamaya yeni başladım ve düşünebileceğim bir kullanım örneği, bir testte sadece tek bir bağlayıcıyı geçersiz kılmak istiyorum. Sanırım her şeyin doğru bir şekilde kurulduğundan emin olmak ve çoğaltmayı önlemek için üretim seviyesi bağlantılarının geri kalanını kullanmak istiyorum.

Aşağıdaki Modüle sahip olduğumu düşünün

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

Ve testimde, InterfaceA ve InterfaceB'yi incelikli tutarken sadece InterfaceC'yi geçersiz kılmak istiyorum, bu yüzden şöyle bir şey istiyorum:

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

Ayrıca, şans olmadan aşağıdakileri denedim:

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

İstediğimi yapmanın mümkün olup olmadığını bilen var mı yoksa tamamen yanlış ağacı havlıyor muyum ??

--- Takip et: Görünüşe göre, arayüzde @ImplementedBy etiketini kullanır ve daha sonra sadece 1-1 eşleme olduğunda güzel çalışan test durumunda bir bağlanma sağlarsam istediğim şeyi başarabilirim. arayüz ve uygulama.

Ayrıca, bunu bir meslektaşımla tartıştıktan sonra, tüm bir modülü geçersiz kılma ve modüllerimizin doğru bir şekilde tanımlanmasını sağlama yolunda ilerliyoruz. Bu, bir bağlantının bir modülde yanlış yerleştirildiği ve taşınması gerektiğinde bir soruna neden olabileceği gibi görünüyor, böylece bağların artık geçersiz kılınamayacağı için muhtemelen bir test yükünü kırıyor.


7
"Yanlış ağacı havlamak" ifadesi gibi: D
Boris Pavlović

Yanıtlar:


149

Aradığınız cevap bu olmayabilir, ancak birim testleri yazıyorsanız, muhtemelen bir enjektör kullanmamalısınız ve bunun yerine sahte veya sahte nesneler elle enjekte etmemelisiniz.

Öte yandan, gerçekten tek bir bağlayıcıyı değiştirmek istiyorsanız, şunları kullanabilirsiniz 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()));

Ayrıntılar için buraya bakın .

Ancak javadoc'un Modules.overrides(..)önerdiği gibi, modüllerinizi bağlamaları geçersiz kılmaya gerek kalmayacak şekilde tasarlamalısınız. Verdiğiniz örnekte, bağlanmasını InterfaceCayrı bir modüle taşıyarak bunu başarabilirsiniz .


9
Teşekkürler Albert, bu beni istediğimi yapmak için yoldan bir yere götürüyor. Bu henüz bir üretim sürümünde! Bu, birim testleri değil, entegrasyon testleri içindir, bu yüzden diğer her şeyin doğru şekilde inşa edildiğinden emin olmak istiyorum
tddmonkey

1
Koda somut bir örnek ekledim. Seni daha da ileriye götürür mü?
albertb

1
Yanılmıyorsam, bunu yaparken ovverideuygun Stageolanı kaybeder (yani KALKINMA sistematik olarak kullanılır).
pdeschen

4
Boyut önemlidir. Bağımlılık grafiğiniz büyüdüğünde, elle kablolama oldukça acı verici olabilir. Ayrıca kablolama değişiklikleri yaparken tüm manuel kablolama yerlerinizi manuel olarak güncellemeniz gerekir. Geçersiz kılma bunu otomatik olarak halletmenizi sağlar.
yoosiba

3
@pdeschen Guice 3'te Guice 4 için düzelttiğim bir hata.
Tavian Barnes

9

Neden miras kullanılmıyor? overrideMeYöntemdeki paylaşılan bağlantıları bırakarak yöntemdeki belirli bağlantılarınızı geçersiz kılabilirsiniz configure.

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

Ve son olarak enjektörünüzü şu şekilde oluşturun:

Guice.createInjector(new TestModule());

3
@Overrideİşe görünmüyor. Özellikle bir @Providesşey böyle bir yöntemle yapılırsa .
Sasanka Panguluri

4

Üretim modülünüzü değiştirmek istemiyorsanız ve varsayılan bir maven benzeri proje yapınız varsa

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

Orijinal ConcreteCdizininizle aynı paketi kullanarak test dizininizde yeni bir sınıf oluşturabilirsiniz . Guice daha sonra test dizininize bağlanacak InterfaceC, ConcreteCdiğer tüm arayüzler ise üretim sınıflarınıza bağlanacaktır.


2

Her test sınıfı için özel yapılandırmanızı bildirebileceğiniz Juckito'yu kullanmak istiyorsunuz .

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

1

Farklı bir kurulumda, ayrı modüllerde tanımlanmış birden fazla etkinliğimiz var. Enjekte edilen etkinlik, AndroidManifest.xml dosyasında kendi RoboGuice modülü tanımına sahip bir Android Kitaplık Modülündedir.

Kurulum böyle görünüyor. Kütüphane Modülünde şu tanımlar vardır:

AndroidManifest.xml:

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

Sonra enjekte edilen bir tip var:

interface Foo { }

Foo'nun bazı varsayılan uygulamaları:

class FooThing implements Foo { }

MainModule, Foo için FooThing uygulamasını yapılandırır:

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

Ve son olarak, Foo tüketen bir Faaliyet:

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

Tüketici Android Uygulama Modülünde kullanmak istiyoruz, SomeActivityancak test amaçlı olarak kendimizi enjekte ediyoruz Foo.

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

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

Modül işlemeyi istemci uygulamasına maruz bırakmak iddia edilebilir, ancak Kütüphane Modülü bir SDK olduğundan ve parçaları açığa çıkarmanın daha büyük etkileri olduğu için çoğunlukla enjekte edilen bileşenleri gizlememiz gerekir.

(Unutmayın, bu test içindir, bu yüzden SomeActivity'nin içsellerini biliyoruz ve (paket görünür) bir Foo tükettiğini biliyoruz).

İşe yaradığını bulduğum yol mantıklı; test için önerilen geçersiz kılmayı kullanın :

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

Şimdi, SomeActivitybaşlatıldığında, OtherFooThingenjekte edilen Fooörneği için alacaktır .

Bizim durumumuzda, test durumlarını kaydetmek için dahili olarak OtherFooThing'in kullanıldığı, FooThing'in varsayılan olarak diğer tüm kullanımlar için kullanıldığı çok spesifik bir durumdur.

Unutmayın, biz vardır kullanarak #newDefaultRoboModulebizim birim testlerinde ve sorunsuz çalışır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.