Adedi ile ilk ve ikinci kez farklı dönüş değerleri


262

Ben böyle bir test var:

    [TestCase("~/page/myaction")]
    public void Page_With_Custom_Action(string path) {
        // Arrange
        var pathData = new Mock<IPathData>();
        var pageModel = new Mock<IPageModel>();
        var repository = new Mock<IPageRepository>();
        var mapper = new Mock<IControllerMapper>();
        var container = new Mock<IContainer>();

        container.Setup(x => x.GetInstance<IPageRepository>()).Returns(repository.Object);

        repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(() => pageModel.Object);

        pathData.Setup(x => x.Action).Returns("myaction");
        pathData.Setup(x => x.Controller).Returns("page");

        var resolver = new DashboardPathResolver(pathData.Object, repository.Object, mapper.Object, container.Object);

        // Act
        var data = resolver.ResolvePath(path);

        // Assert
        Assert.NotNull(data);
        Assert.AreEqual("myaction", data.Action);
        Assert.AreEqual("page", data.Controller);
    }

GetPageByUrlbenim iki kez çalışır DashboardPathResolver, Moq nullilk kez ve pageModel.Objectikinci dönmek için nasıl söyleyebilirim ?

Yanıtlar:


453

Moq'un en son sürümüyle (4.2.1312.1622), SetupSequence'ı kullanarak bir dizi olay ayarlayabilirsiniz . İşte bir örnek:

_mockClient.SetupSequence(m => m.Connect(It.IsAny<String>(), It.IsAny<int>(), It.IsAny<int>()))
        .Throws(new SocketException())
        .Throws(new SocketException())
        .Returns(true)
        .Throws(new SocketException())
        .Returns(true);

Connect çağrısı yalnızca üçüncü ve beşinci denemede başarılı olur, aksi takdirde bir istisna atılır.

Yani örneğin, sadece şöyle bir şey olurdu:

repository.SetupSequence(x => x.GetPageByUrl<IPageModel>(virtualUrl))
.Returns(null)
.Returns(pageModel.Object);

2
Güzel cevap, tek sınırlama "SetupSequence" korumalı üyeleri ile çalışmaz.
Chasefornone

7
Ne yazık ki, ile SetupSequence()çalışmıyor Callback(). Eğer öyleyse, alay konusu yönteme yapılan çağrıları "durum makinesi" tarzında doğrulayabilir.
urig

@stackunderflow SetupSequenceyalnızca iki çağrı için çalışır, ancak ikiden fazla çağrıya ihtiyaç duyarsam ne yapabilirim?
TanvirArjel

@TanvirArjel, ne demek istediğinden emin değilim ... SetupSequencerastgele sayıda arama için kullanılabilir. Verdiğim ilk örnek 5 çağrı dizisini döndürüyor.
stackunderflow

@stackunderflow Üzgünüm! Bu benim yanlış anlaşılmamdı! Evet! Beklediğiniz gibi çalışıyorsunuz!
TanvirArjel

115

Mevcut cevaplar harika, ama sadece kullanan System.Collections.Generic.Queueve alaycı çerçeve hakkında herhangi bir özel bilgi gerektirmeyen alternatifimi atacağımı sanıyordum - çünkü yazdığımda hiç yoktu! :)

var pageModel = new Mock<IPageModel>();
IPageModel pageModelNull = null;
var pageModels = new Queue<IPageModel>();
pageModels.Enqueue(pageModelNull);
pageModels.Enqueue(pageModel.Object);

Sonra...

repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(pageModels.Dequeue);

Teşekkürler. Ben sadece pageModel.Object yerine pageModel sahte enqueueing yazım hatası düzeltildi, bu yüzden şimdi de inşa gerekir! :)
mo.

3
Cevap doğru olmakla birlikte, bir atmak istiyorsanız notu bu çalışmaz Exceptionbunu yapamazsınız olarak Enqueueo. Ancak SetupSequenceişe yarayacaktır (örneğin @stackunderflow'un cevabına bakınız).
Halvard

4
Dequeue için yetki verilen bir yöntem kullanmanız gerekir. Numunenin yazılma şekli, kuyruk, kurulum sırasında değerlendirildiği için kuyruktaki ilk öğeyi tekrar tekrar döndürür.
Jason Coyne

7
Bu bir delege. Kod Dequeue()yerine sadece içeriyorsa Dequeue, doğru olursunuz.
mo.

31

Geri arama eklemek benim için işe yaramadı, bunun yerine http://haacked.com/archive/2009/09/29/moq-sequences.aspx yerine bu yaklaşımı kullandım ve bunun gibi bir testle sonuçlandım:

    [TestCase("~/page/myaction")]
    [TestCase("~/page/myaction/")]
    public void Page_With_Custom_Action(string virtualUrl) {

        // Arrange
        var pathData = new Mock<IPathData>();
        var pageModel = new Mock<IPageModel>();
        var repository = new Mock<IPageRepository>();
        var mapper = new Mock<IControllerMapper>();
        var container = new Mock<IContainer>();

        container.Setup(x => x.GetInstance<IPageRepository>()).Returns(repository.Object);
        repository.Setup(x => x.GetPageByUrl<IPageModel>(virtualUrl)).ReturnsInOrder(null, pageModel.Object);

        pathData.Setup(x => x.Action).Returns("myaction");
        pathData.Setup(x => x.Controller).Returns("page");

        var resolver = new DashboardPathResolver(pathData.Object, repository.Object, mapper.Object, container.Object);

        // Act
        var data = resolver.ResolvePath(virtualUrl);

        // Assert
        Assert.NotNull(data);
        Assert.AreEqual("myaction", data.Action);
        Assert.AreEqual("page", data.Controller);
    }

29

Sahte nesnenizi ayarlarken geri arama kullanabilirsiniz. Adedi Wiki'den ( http://code.google.com/p/moq/wiki/QuickStart ) örneğe bir göz atın .

// returning different values on each invocation
var mock = new Mock<IFoo>();
var calls = 0;
mock.Setup(foo => foo.GetCountThing())
    .Returns(() => calls)
    .Callback(() => calls++);
// returns 0 on first invocation, 1 on the next, and so on
Console.WriteLine(mock.Object.GetCountThing());

Kurulumunuz şöyle görünebilir:

var pageObject = pageModel.Object;
repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(() => pageObject).Callback(() =>
            {
                // assign new value for second call
                pageObject = new PageModel();
            });

1
Bunu yaptığımda iki kere null alıyorum: var pageModel = new Mock <IPageModel> (); IPageModel modeli = null; repository.Setup (x => x.GetPageByUrl <IPageModel> (yol)). (() => model) .Callback (() => {model = pageModel.Object;});
marcus

Resolver.ResolvePath yönteminde GetPageByUrl iki kez çağrılıyor mu?
Dan

ResolvePath aşağıdaki kodu içerir, ancak yine de iki kere null var foo = _repository.GetPageByUrl <IPageModel> (virtualUrl); var foo2 = _repository.GetPageByUrl <IPageModel> (virtualUrl);
marcus

2
Geri arama yaklaşımının işe yaramadığını doğruladı (önceki Moq sürümünde bile denendi). Testinize bağlı olarak bir başka olası yaklaşım da sadece Setup()aramayı tekrar yapmak ve Return()farklı bir değerdir.
Kent Boogaart


4

Biraz farklı gereksinimle aynı tür bir sorun için buraya ulaştı.
Ben farklı giriş değerleri ve sahte moq'ın bildirim sözdizimi (linq to Mocks) kullandığından IMO daha okunabilir çözüm bulundu farklı dönüş değerleri almak gerekir .

public interface IDataAccess
{
   DbValue GetFromDb(int accountId);  
}

var dataAccessMock = Mock.Of<IDataAccess>
(da => da.GetFromDb(It.Is<int>(acctId => acctId == 0)) == new Account { AccountStatus = AccountStatus.None }
&& da.GetFromDb(It.Is<int>(acctId => acctId == 1)) == new DbValue { AccountStatus = AccountStatus.InActive }
&& da.GetFromDb(It.Is<int>(acctId => acctId == 2)) == new DbValue { AccountStatus = AccountStatus.Deleted });

var result1 = dataAccessMock.GetFromDb(0); // returns DbValue of "None" AccountStatus
var result2 = dataAccessMock.GetFromDb(1); // returns DbValue of "InActive"   AccountStatus
var result3 = dataAccessMock.GetFromDb(2); // returns DbValue of "Deleted" AccountStatus

Benim için (2019'dan itibaren 4.13.0 Adedi), daha kısa da.GetFromDb(0) == new Account { ..None.. && da.GetFromDb(1) == new Account { InActive } && ..., hayır It.Is-lambda ile bile işe yaramadı .
ojdo

3

Kabul edilen yanıtın yanı sıra SetupSequence yanıtı da dönen sabitleri işler.

Returns()alay edilen yönteme gönderilen parametrelere dayalı bir değer döndürebileceğiniz bazı faydalı aşırı yüklemelere sahiptir. Kabul edilen cevapta verilen çözüme dayanarak, bu aşırı yüklemeler için başka bir uzatma yöntemi.

public static class MoqExtensions
{
    public static IReturnsResult<TMock> ReturnsInOrder<TMock, TResult, T1>(this ISetup<TMock, TResult> setup, params Func<T1, TResult>[] valueFunctions)
        where TMock : class
    {
        var queue = new Queue<Func<T1, TResult>>(valueFunctions);
        return setup.Returns<T1>(arg => queue.Dequeue()(arg));
    }
}

Ne yazık ki, yöntemi kullanmak için bazı şablon parametreleri belirtmeniz gerekir, ancak sonuç yine de okunabilir.

repository
    .Setup(x => x.GetPageByUrl<IPageModel>(path))
    .ReturnsInOrder(new Func<string, IPageModel>[]
        {
            p => null, // Here, the return value can depend on the path parameter
            p => pageModel.Object,
        });

Birden çok parametre ile (uzatma yöntemi için aşırı yüklenmeleri oluşturun T2, T3vb) gerekirse.

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.