2011-09-02 11 views
150

mam test takiego:Different zwracają wartości pierwszy i drugi raz z Min

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

GetPageByUrl biegnie dwa razy w moim dashboardpathresolver, jak mogę powiedzieć Min wrócić zerową po raz pierwszy i pageModel.Ojbect się druga?

Odpowiedz

27

Dodawanie zwrotna nie działa dla mnie, użyłem tego podejścia zamiast http://haacked.com/archive/2009/09/29/moq-sequences.aspx i skończyło się z testem jak ten:

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

Możesz użyć wywołania zwrotnego podczas konfigurowania symulowanego obiektu. Spójrz na przykład z Wiki Moq (http://code.google.com/p/moq/wiki/QuickStart).

// 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()); 

Twoja konfiguracja może wyglądać następująco:

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

Otrzymuję wartość null zarówno w czasie, gdy to robię: var pageModel = new Mock (); Model IPageModel = null; Repository.Setup (x => x.GetPageByUrl (ścieżka)). Zwraca (() = model) .Callback (() => { model = pageModel.Object; }); – Marcus

+0

Czy GetPageByUrl jest wywoływana dwa razy w metodzie resolver.ResolvePath? – Dan

+0

Tak, jest on wywoływany dwa razy: – Marcus

92

Istniejące odpowiedzi są świetne, ale pomyślałem, że wrzucę moją alternatywę, która po prostu używa System.Collections.Generic.Queue i nie wymaga specjalnej wiedzy o szyderczym środowisku - ponieważ nie miałem żadnych, kiedy Napisałem to! :)

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

Potem ...

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

Dzięki. Właśnie naprawiłem literówkę, w której zapisałem skrypt stronyModel zamiast obiektu pageModel.Object, więc teraz powinien on nawet zostać zbudowany! :) –

+3

Odpowiedź jest poprawna, ale zauważ, że to nie zadziała, jeśli chcesz rzucić 'wyjątek', ponieważ nie możesz go" podkatalogować ". Ale 'SetupSequence' będzie działało (patrz na przykład odpowiedź @stackunderflow). – Halvard

+2

Musisz użyć metody delegowanej dla Dequeue. Sposób, w jaki zostanie zapisana próbka, zawsze będzie zawsze zwracał pierwszy element w kolejce, ponieważ kolejka jest usuwana podczas konfiguracji. –

262

Dzięki najnowszej wersji Min (4.2.1312.1622), można konfiguracji, sekwencja zdarzeń z wykorzystaniem SetupSequence. Oto przykład:

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

Wywołanie łączyć będą skuteczne na trzeciej i piątej próbie, w przeciwnym razie zostanie wyrzucony wyjątek tylko.

Tak dla przykładu byłoby to po prostu coś takiego:

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

Dobra odpowiedź, jedynym ograniczeniem jest "SetupSequence" nie działa z chronionymi członkami. – Chasefornone

+1

Niestety, 'SetupSequence()' nie działa z 'Callback()'. Gdyby tak było, można zweryfikować wywołania do wyśmiewanej metody w sposób "automatu stanów". – urig

1

osiągnęła tutaj tego samego rodzaju problem z nieco innej wymogu.
Potrzebuję uzyskać różnych wartości zwracanych z próbnych w oparciu o różne wartości wejściowe i znalazłem rozwiązanie, które IMO jest bardziej czytelne, ponieważ używa deklaratywnej składni Moq (linq to Mocks).

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 
0

The accepted answer, jak również SetupSequence answer, uchwyty powrocie stałe.

Returns() ma pewne przydatne przeciążenia, w których można zwrócić wartość na podstawie parametrów wysłanych do wyśmiewanej metody. Opierając się na the solution podanej w zaakceptowanej odpowiedzi, oto kolejna metoda rozszerzenia tych przeciążeń.

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

Niestety, użycie tej metody wymaga podania niektórych parametrów szablonu, ale wynik jest nadal czytelny.

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

Tworzenie przeciążeń dla metodę rozszerzenia z wieloma parametrami (T2, T3, etc) w razie potrzeby.

Powiązane problemy