Mockito Sadece süper sınıfın bir yönteminin çağrısıyla nasıl alay edilir


97

Mockito'yu bazı testlerde kullanıyorum.

Aşağıdaki sınıflarım var:

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        //some code  
        super.save();
    }  
}   

Sadece ikinci çağrısıyla ( super.save) alay etmek istiyorum ChildService. İlk çağrı gerçek yöntemi çağırmalıdır. Bunu yapmanın bir yolu var mı?


Bu PowerMockito ile çözülebilir mi?
javaPlease42

@ javaPlease42: Evet şunları yapabilirsiniz: stackoverflow.com/a/23884011/2049986 .
Jacob van Lingen

Yanıtlar:


57

Hayır, Mockito bunu desteklemiyor.

Aradığınız cevap bu olmayabilir, ancak gördüğünüz şey tasarım ilkesini uygulamamanın bir belirtisidir:

Miras yerine kompozisyonu tercih edin

Bir süper sınıfı genişletmek yerine bir strateji çıkarırsanız sorun ortadan kalkar.

Bununla birlikte, kodu değiştirmenize izin verilmiyorsa, ancak yine de test etmeniz gerekiyorsa ve bu garip şekilde hala umut var. Bazı AOP araçlarıyla (örneğin AspectJ), kodu süper sınıf yöntemine dokuyabilir ve çalıştırılmasını tamamen önleyebilirsiniz (yuck). Proxy kullanıyorsanız bu işe yaramaz, bayt kodu modifikasyonu kullanmanız gerekir (yükleme zamanı dokuma veya derleme zamanı dokuma). PowerMock ve PowerMockito gibi bu tür hileyi destekleyen alaycı çerçeveler de vardır.

Yeniden düzenlemeye gitmenizi öneririm, ancak bu bir seçenek değilse, ciddi bir bilgisayar korsanlığı eğlencesine katılabilirsiniz.


5
LSP ihlalini görmüyorum. OP ile kabaca aynı kuruluma sahibim: findAll () yöntemine sahip bir temel DAO sınıfı ve super.findAll () yöntemini çağırarak ve ardından sonucu sıralayarak temel yöntemi geçersiz kılan bir alt sınıf DAO. Alt sınıf, üst sınıfı kabul eden tüm bağlamlarda ikame edilebilir. Anlamını yanlış mı anlıyorum?

1
LSP açıklamasını kaldıracağım (cevaba değer katmaz).
iwein

Evet, miras berbat ve takıldığım aptal çerçeve, miras tek seçenek olarak tasarlandı.
Sridhar Sarnobat

Süper sınıfı yeniden tasarlayamayacağınızı varsayarsak, //some codeskodu ayrı ayrı test edilebilecek bir yönteme çıkartabilirsiniz.
Phasmal

1
Tamam anladım. Bunu aradığımda çözmeye çalıştığımdan farklı bir sorun, ama en azından bu yanlış anlama, temel sınıftan çağrı alay etmek için kendi sorunumu çözdü (gerçekten geçersiz kılmıyorum).
Guillaume Perrot

91

Yeniden düzenleme için gerçekten bir seçeneğiniz yoksa, süper yöntem çağrısındaki her şeyle dalga geçebilir / saplama yapabilirsiniz.

    class BaseService {

        public void validate(){
            fail(" I must not be called");
        }

        public void save(){
            //Save method of super will still be called.
            validate();
        }
    }

    class ChildService extends BaseService{

        public void load(){}

        public void save(){
            super.save();
            load();
        }
    }

    @Test
    public void testSave() {
        ChildService classToTest = Mockito.spy(new ChildService());

        // Prevent/stub logic in super.save()
        Mockito.doNothing().when((BaseService)classToTest).validate();

        // When
        classToTest.save();

        // Then
        verify(classToTest).load();
    }

2
bu kod aslında super.save () çağrısını engellemez, bu yüzden super.save () 'de çok şey yaparsanız, tüm bu çağrıları engellemeniz gerekir ...
iwein

Harika bir çözüm, çocuğun kullanması için bir süper sınıf yönteminden alay konusu bir değeri iade etmek istediğimde benim için harikalar yaratıyor, harika teşekkürler.
Gurnard

14
validate gizli olmadığı veya kaydetme yöntemi başka bir yöntemi çağırmak yerine doğrudan çalışmadığı sürece bu iyi çalışır. mockito şunları yapmaz: Mockito.doNothing (). ((BaseService) casus olduğunda) .save (); bu, temel hizmette hiçbir şey kaydetmeyecek, ancak çocuk hizmetinde kaydetmeyecektir :(
tibi

Bunun benimkinde çalışmasını sağlayamıyorum - çocuk yöntemini de ortaya çıkarıyor. BaseServicesoyut, ancak bunun neden alakalı olacağını anlamıyorum.
Sridhar Sarnobat

1
@ Sridhar-Sarnobat evet aynı şeyi görüyorum :( bunu nasıl elde edeceğini bilen var super.validate()mı?
stantonk

5

ChildService.save () yöntemindeki kodu farklı bir yöntemle yeniden düzenlemeyi düşünün ve ChildService.save () 'yi test etmek yerine bu yeni yöntemi test edin, bu şekilde super yöntemine gereksiz çağrı yapılmasını önleyeceksiniz.

Misal:

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        newMethod();    
        super.save();
    }
    public void newMethod(){
       //some codes
    }
} 

1

süper sınıf yöntemini çağıran alt sınıfta bir paket korumalı (aynı pakette test sınıfını varsayar) yöntemi oluşturun ve ardından bu yöntemi geçersiz kılınmış alt sınıf yönteminizde çağırın. daha sonra casus modelini kullanarak testinizde bu yöntemle ilgili beklentileri belirleyebilirsiniz. güzel değil ama kesinlikle testinizdeki süper yönteme ilişkin tüm beklenti ayarlarıyla uğraşmaktan daha iyi


Ayrıca, kalıtım yerine kompozisyonun neredeyse her zaman daha iyi olduğunu söyleyebilir miyim, ancak bazen kalıtımın kullanılması daha basittir. java, scala veya groovy gibi daha iyi bir kompozisyon modelini birleştirene kadar, durum her zaman böyle olacak ve bu sorun var olmaya devam edecek
Luke

1

İwein yanıtına tamamen katılıyorum bile (

miras yerine kompozisyonu tercih etmek

), bazı zamanlar kalıtımın sadece doğal göründüğünü kabul ediyorum ve sadece bir birim testi uğruna onu kırma veya yeniden düzenleme gibi hissetmiyorum.

Öyleyse, benim önerim:

/**
 * BaseService is now an asbtract class encapsulating 
 * some common logic callable by child implementations
 */
abstract class BaseService {  
    protected void commonSave() {
        // Put your common work here
    }

    abstract void save();
}

public ChildService extends BaseService {  
    public void save() {
        // Put your child specific work here
        // ...

        this.commonSave();
    }  
}

Ve sonra, birim testinde:

    ChildService childSrv = Mockito.mock(ChildService.class, Mockito.CALLS_REAL_METHODS);

    Mockito.doAnswer(new Answer<Void>() {
        @Override
        public Boolean answer(InvocationOnMock invocation)
                throws Throwable {
            // Put your mocked behavior of BaseService.commonSave() here
            return null;
        }
    }).when(childSrv).commonSave();

    childSrv.save();

    Mockito.verify(childSrv, Mockito.times(1)).commonSave();

    // Put any other assertions to check child specific work is done

0

Bunun nedeni, temel sınıfınızın halka açık olmamasıdır, o zaman Mockito görünürlük nedeniyle onu engelleyemez, eğer temel sınıfı public olarak değiştirirseniz veya alt sınıfta (genel olarak) @Override, Mockito doğru şekilde alay edebilir.

public class BaseService{
  public boolean foo(){
    return true;
  }
}

public ChildService extends BaseService{
}

@Test
@Mock ChildService childService;
public void testSave() {
  Mockito.when(childService.foo()).thenReturn(false);

  // When
  assertFalse(childService.foo());
}

8
Konu bu değil. ChildService foo () 'yu geçersiz kılmalıdır ve sorun BaseService.foo () ile nasıl dalga geçileceğidir, ancak ChildService.foo () değil
Adriaan Koster

0

Kalıtım mantıklıysa belki de en kolay seçenek, süper'i çağırmak için yeni bir yöntem (özel paket ??) oluşturmaktır (hadi ona superFindall diyelim), gerçek örneği gözetlemek ve ardından alay etmek istediğiniz şekilde superFindAll () yöntemiyle dalga geçmek ebeveyn sınıf bir. Kapsama ve görünürlük açısından mükemmel bir çözüm değil ama işi yapmalı ve uygulaması kolay.

 public Childservice extends BaseService {
    public void save(){
        //some code
        superSave();
    }

    void superSave(){
        super.save();
    }
}
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.