2010-06-20 6 views
4

J'essaie d'utiliser Rhino.Mocks pour simuler un objet ControllerContext pour accéder à des objets d'exécution comme Utilisateur, Demande, Réponse et Session dans mes tests unitaires de contrôleur. J'ai écrit la méthode ci-dessous dans une tentative de simuler un contrôleur.Comment utiliser Rhino.Mocks pour simuler un ControllerContext

private TestController CreateTestControllerAs(string userName) 
{ 
    var mock = MockRepository.GenerateStub<ControllerContext>(); 
    mock.Stub(con => 
     con.HttpContext.User.Identity.Name).Return(userName); 
    mock.Stub(con => 
     con.HttpContext.Request.IsAuthenticated).Return(true); 

    var controller = CreateTestController(); // left out of example for brevity 
    controller.ControllerContext = mock; 

    return controller; 
} 

Cependant, la HttpContext de mon moqué ControllerContext est nulle et il mes tentatives d'accès HttpContext.User etc. provoquer une System.NullReferenceException.

Qu'est-ce que je fais de mal avec mon moqueur?

Répondre

5

Je vous recommande fortement de regarder MVCContrib.TestHelper qui utilise Rhino.Mocks et fournit une façon élégante de tester vos contrôleurs. Voici comment votre test pourrait ressembler à:

[TestClass] 
public class UsersControllerTests : TestControllerBuilder 
{ 
    [TestMethod] 
    public void UsersController_Index() 
    { 
     // arrange 
     // TODO : this initialization part should be externalized 
     // so that it can be reused by other tests 
     var sut = new HomeController(); 
     this.InitializeController(sut); 
     // At this point sut.Request, sut.Response, sut.Session, ... are 
     // stubed objects on which you could define expectations. 

     // act 
     var actual = sut.Index(); 

     // assert 
     actual.AssertViewRendered(); 
    } 
} 

Et voici un unit test pour une controller qui fait partie d'un sample ASP.NET MVC application je l'ai écrit.

+0

Cela a bien fonctionné. Toujours dû mettre un objet dans la propriété 'HttpContext.User' mais cela a été facilement accompli en faisant:' HttpContext.User = new GenericPrincipal (new GenericIdentity (loginName), null); ' – ahsteele

0

Je crois que le problème est que vous devez bouchonner la chaîne de propriétés, ou au moins passer à votre ControllerContext Mock un HttpContext, à savoir quelque chose le long des lignes de:

private TestController CreateTestControllerAs(string userName) 
{ 
    var mock = MockRepository.GenerateStub<ControllerContext>(); 
    var context = MockRepository.GenerateStub<IHttpContext>();  
    mock.Stub(con => 
     con.HttpContext).Return(context); 
    // etc... with User, Identity ... 

    return controller; 
} 

Dans votre code, étant donné que vous ne définissez jamais le HttpContext de manière spécifique, par défaut votre Stub suppose que c'est null.

Je n'ai pas utilisé la solution décrite par Darin, mais il semble que cela vous faciliterait la vie!

1

Les autres réponses ont déjà montré comment vous pouvez vous moquer d'une chaîne de propriété pour contourner votre problème.

Mais le vrai problème ici est que les tests unitaires et les moqueurs ne fonctionnent pas vraiment bien si vous ne respectez pas le law of demeter. Si vous voulez que votre code soit testable et réutilisable au maximum, vous devez injecter directement les dépendances réelles de votre code et cacher ces dépendances derrière les abstractions.

Par exemple, au lieu de le faire:

public class MyClass 
{ 
    public ControllerContext Context { get; set; } 

    public void DoSomething() 
    { 
     // BAD: we're only interested in the name, but instead we've injected 
     // a ControllerContext that can give us a HttpContext that can give us 
     // a User that can give us an Identity that can give us the Name. 
     string name = Context.HttpContext.User.Identity.Name; 
     // etcetera 
    } 
} 

Faites ceci:

public class MyClass 
{ 
    public INameProvider NameProvider { get; set; } 

    public void DoSomething() 
    { 
     // GOOD: we've injected a name provider 
     string name = NameProvider.Name; 
     // etcetera 
    } 
} 

En introduisant le concept INameProvider, votre code de composants, tests et simulacres deviennent beaucoup plus simples. Votre code devient également plus réutilisable: il ne dépend que du concept abstrait d'un "fournisseur de nom", plutôt que d'un tas de classes ASP.NET. Vous serez en mesure de réutiliser votre composant dans n'importe quel environnement tant qu'il est possible d'implémenter un adaptateur INameProvider. Le compromis est que vous aurez besoin de déclarer l'interface INameProvider et d'écrire une classe wrapper qui l'implémente. Lorsque vous suivez systématiquement cette approche, vous vous retrouverez avec de nombreuses petites interfaces et classes d'adaptateurs. Telle est la voie du développement piloté par les tests.

(Si vous vous demandez pourquoi j'introduis INameProvider au lieu de définir le nom directement - c'est pour que le conteneur IoC puisse utiliser l'interface pour faire correspondre la dépendance avec l'implémentation.

+0

Ceci est une approche commune, mais conduira à la duplication de la couche mvc avec un code personnalisé. c'est-à-dire INameProvider pour l'utilisateur, les classes de service pour les contrôleurs, la session ... les cookies, la requête, la réponse et plus problématiques à résumer sont les résultats de l'action. –

Questions connexes