Adedi: Alaycı hizmet yöntemine iletilen bir parametreye nasıl gidilir


170

Bu sınıfı hayal edin

public class Foo {

    private Handler _h;

    public Foo(Handler h)
    {
        _h = h;
    }

    public void Bar(int i)
    {
        _h.AsyncHandle(CalcOn(i));
    }

    private SomeResponse CalcOn(int i)
    {
        ...;
    }
}

Mo (q) Hando'yu bir Foo testinde cking, neyin Bar()geçtiğini nasıl kontrol edebilirdim _h.AsyncHandle?


Şunu mu demek istediniz: "AsyncHandle" (extra "n")? İşleyici kodunu gönderebilir veya standart bir türse tam tür adını belirtebilir misiniz?
TrueWill

Ne düşündüğünüzü göstermek için iskelet testinizi gösterebilir misiniz? Sizin açınızdan anlaşılıyor olsa da, bizim açımızdan, uzun bir spekülatif cevap vermeden soruyu cevaplanabilir hale getirmek için zaman ayırmayan biri gibi görünüyor.
Ruben Bartelink

1
Ne Foo ne de Bar () ne de böyle bir şey yok. Sadece uygulama durumunu dikkat dağıtmadan içinde bulunduğum durumu göstermek için bazı demo kodu. Ve sadece cevabı aldım, almayı umuyordum.
Ocak

Yanıtlar:


284

Mock.Callback yöntemini kullanabilirsiniz:

var mock = new Mock<Handler>();
SomeResponse result = null;
mock.Setup(h => h.AnsyncHandle(It.IsAny<SomeResponse>()))
    .Callback<SomeResponse>(r => result = r);

// do your test
new Foo(mock.Object).Bar(22);
Assert.NotNull(result);

Eğer sadece argümandaki basit bir şeyi kontrol etmek istiyorsanız, bunu doğrudan da yapabilirsiniz:

mock.Setup(h => h.AnsyncHandle(It.Is<SomeResponse>(response => response != null)));

36
Yan not, işlevinizde birden çok bağımsız değişken varsa, genel Callback<>()Moq yöntemindeki tüm türleri belirtmeniz gerekir . Örneğin, metodun tanımına sahip Handler.AnsyncHandle(string, SomeResponse)olsaydınız, ihtiyacınız olurdu /* ... */.Callback<string, SomeResponse>(r => result = r);. Bunu açıkça birçok yerde bulamadım, bu yüzden buraya ekleyeceğimi düşündüm.
Frank Bryce

12
@JavaJudt yanıtını görmediyseniz @Frank düzeltmek istedim. İki argüman almak için doğru yolu:/* ... */.Callback<string, SomeResponse>((s1, s2) => { str1 = s1; result = s2});
renatogbp

2
Aynı şekilde lambda ifadesindeki argüman türlerini bildirerek de başarabilirsiniz:.Callback((string s1, SomeResponse s2) => /* stuff */ )
jb637

1
Yerleşik Capture.Inyardımcıya referans eklemek için yanıtınızı güncelleyebilir misiniz ?
cao

29

Gamlor'un yanıtı benim için çalıştı, ama John Carpenter'ın yorumunu genişleteceğimi düşündüm çünkü birden fazla parametre içeren bir çözüm arıyordum. Bu sayfaya rastlayan diğer kişilerin de benzer bir durumda olabileceğini düşündüm. Bu bilgiyi Adedi belgelerinde buldum .

Gamlor örneğini kullanacağım, ancak AsyncHandle yönteminin iki argüman aldığını varsayalım: a stringve bir SomeResponsenesne.

var mock = new Mock<Handler>();
string stringResult = string.Empty;
SomeResponse someResponse = null;
mock.Setup(h => h.AsyncHandle(It.IsAny<string>(), It.IsAny<SomeResponse>()))
    .Callback<string, SomeResponse>((s, r) => 
    {
        stringResult = s;
        someResponse = r;
    });

// do your test
new Foo(mock.Object).Bar(22);
Assert.AreEqual("expected string", stringResult);
Assert.IsNotNull(someResponse);

Temel olarak It.IsAny<>(), uygun tipte başka bir tane eklemeniz, Callbackyönteme başka bir tür eklemeniz ve lambda ifadesini uygun şekilde değiştirmeniz gerekir.


23

Geri Arama yöntemi kesinlikle işe yarayacaktır, ancak bunu çok sayıda parametreye sahip bir yöntemle yapıyorsanız, biraz ayrıntılı olabilir. İşte kazanın bir kısmını çıkarmak için kullandığım bir şey.

var mock = new Mock<Handler>();

// do your test   
new Foo(mock.Object).Bar(22);

var arg = new ArgumentCaptor<SomeResponse>();
mock.Verify(h => h.AsyncHandle(arg.Capture()));
Assert.NotNull(arg.Value);

İşte ArgumentCaptor kaynağı:

public class ArgumentCaptor<T>
{
    public T Capture()
    {
        return It.Is<T>(t => SaveValue(t));
    }

    private bool SaveValue(T t)
    {
        Value = t;
        return true;
    }

    public T Value { get; private set; }
}

21

Gamlor'un cevabı işe yarıyor, ancak bunu yapmanın başka bir yolu (ve testte daha anlamlı olduğunu düşündüğüm bir yöntem) ...

var mock = new Mock<Handler>();
var desiredParam = 47; // this is what you want to be passed to AsyncHandle
new Foo(mock.Object).Bar(22);
mock.Verify(h => h.AsyncHandle(desiredParam), Times.Once());

Doğrulama çok güçlüdür ve alışmak için zaman ayırmaya değer.


15
Bilinen bir parametreyle bir yöntemin çağrılıp çağrılmadığını kontrol etmek istiyorsanız bu yaklaşım uygundur. Parametrenin testi yazma noktasında henüz oluşturulmadığı durumda (örneğin, söz konusu birim parametreyi dahili olarak oluşturur), Geri Arama bunu yakalamanızı ve sorgulamanızı sağlar, ancak yaklaşımınız bunu yapmaz.
Michael

1
Ben geçti değeri saklamak gerekir çünkü tüm nesneleri geçen bir dizi doğrulamak gerekir.
MrFox

Ayrıca, bazen parametre bir varlıktır ve varlıktaki her alan için ayrı ekler istersiniz. Bunu yapmanın en iyi yolu, Verify ve bir eşleştirici kullanmak yerine parametreyi yakalamaktır.
Kevin Wong

13

Alternatif de Capture.Inözelliğini kullanmaktır moq. moqKoleksiyonda argüman yakalamayı sağlayan OOTB özelliğidir.

//Arrange
var args = new List<SomeResponse>();
mock.Setup(h => h.AnsyncHandle(Capture.In(args)));

//Act
new Foo(mock.Object).Bar(22);

//Assert
//... assert args.Single() or args.First()

1
Mükemmel cevap! Bu cevabı görene kadar Moq.Capture ve Moq.CaptureMatch sınıflarının farkında değildim. Yakalama CallbackIMO'ya daha iyi bir alternatiftir . Capture'ı doğrudan parametre listesinde kullandığınız için, bir yöntemin parametre listesini yeniden düzenlerken sorunlara çok daha az eğilimlidir ve bu nedenle testleri daha az kırılgan hale getirir. Geri Arama ile, Kur'a iletilen parametreleri Geri Arama için kullanılan tür parametreleriyle senkronize tutmanız gerekir ve bu kesinlikle geçmişte sorunlara neden oldu.
Justin Holzer

7

It.Is<TValue>()Eşleştirici kullanabilirsiniz .

var mock = new Mock<Handler>();
new Foo(mock.Object).Bar(22);
mock.Verify(h => h.AsyncHandle(It.Is<SomeResponse>(r => r != null )));

2

Bu ayrıca işe yarar:

Mock<InterfaceThing> mockedObject = new Mock<InterfaceThing>();
var objectParameter = mockedObject.Invocations[1].Arguments[0] as ObjectParameter;

2

Burada çok iyi cevaplar var! Bağımlılıklarınıza iletilen çeşitli sınıf parametreleri hakkında iddialarda bulunmanız gerekene kadar kutunun dışında Moq özellik setiyle devam edin. Yine de bu durumda sonuç verirseniz, Eşitleme ile ilgili Adedi Doğrulama özelliği. uzun testler benim için uygun değildir).

İşte bir özeti: https://gist.github.com/Jacob-McKay/8b8d41ebb9565f5fca23654fd944ac6b Bir Moq (4.12) uzantısı ile, yukarıda belirtilen dezavantajlar olmadan, alaylara aktarılan argümanlar hakkında daha beyan edici bir yol veren yazdım. Doğrula bölümü şu şekilde görünüyor:

        mockDependency
            .CheckMethodWasCalledOnce(nameof(IExampleDependency.PersistThings))
            .WithArg<InThing2>(inThing2 =>
            {
                Assert.Equal("Input Data with Important additional data", inThing2.Prop1);
                Assert.Equal("I need a trim", inThing2.Prop2);
            })
            .AndArg<InThing3>(inThing3 =>
            {
                Assert.Equal("Important Default Value", inThing3.Prop1);
                Assert.Equal("I NEED TO BE UPPER CASED", inThing3.Prop2);
            });

Moq deklaratif ve aynı şekilde başarısızlık izolasyonu sağlarken aynı şeyi başaran bir özellik sağlamış olsaydı stoklanacaktır. Parmaklar geçti!


1
Bunu severim. Moq Doğrulaması, beyan gerçekleştirme yetkisi için xUnit Assert ile rekabet eder. Bu, kurulumun Adedi kısmında doğru gelmiyor. It.Is özellik kurulumu da olmayan ifadeleri desteklemelidir.
Thomas

"Adedi xUnit Assert ile iddiaları yerine getirme yetkisi için yarışıyor" - Peki @Thomas. Rekabeti kaybedeceğini de ekleyeceğim. Adedi eşleşen bir çağrı olup olmadığını bildirmek iyidir, ancak belirli iddialar size çok daha iyi bilgi verir. Yaklaşımımın ana dezavantajı, parametrelerin tip güvenliğini ve sipariş kontrolünü kaybetmektir. Bir süredir bu konuda bir iyileştirme arıyordum, umarım dışarıda bir örneği hackleyebilecek bir C # ninja var! Aksi takdirde bir yol bulursam bunu güncelleyeceğim.
Jacob McKay
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.