This is a migrated thread and some comments may be shown as answers.

Simple Service Mocking

8 Answers 238 Views
General Discussions
This is a migrated thread and some comments may be shown as answers.
Stacey
Top achievements
Rank 1
Stacey asked on 14 Apr 2011, 02:34 PM

I have a simple 'Service' system set up with an interface as shown below. I am trying to mock it for use in my unit testing, but am having a bit of an obstacle. The way it works is that I design classes that implement IRequestFor<T,R> and I would call the service bus like this...

var member = new Member { Name = "valid@email.com", Password = "validPassword" };ServiceBus.Query<ValidateUser>().With(member);

This works fine in my code. I have no issues with it. But when I try to mock it, like this ..

var service = Mock.Create<IServiceBus>();

           
// Model
           
var model = new Web.Models.Membership.Login
           
{
               
Email = "acceptible@email.com",
               
Password = "acceptiblePassword",
               
RememberMe = true
           
};

           
// Arrange
           
Mock.Arrange(() => service.Query<Membership.Messages.ValidateMember>().With(model))
               
.Returns(true);

I am given the following error.

NullReferenceException

I don't even know what the exception is on. It 'points' to the ServiceBus in my Controller code, and if I use the debugger, the object is like .. {IServiceBus_Proxy_2718486e043f432da4b143c257cef8ce}, but other than that, everything else looks the exact same as if I step through it in a normal run.

I am using Telerik JustMock for the mocking, but I don't know how I would do this in a different mocking framework either. I am using Ninject for my Dependency Injection, as well. Can anyone help me?

For convenience, I have included as much of my code as possible below.

Code Reference

Service Bus

public interface IServiceBus
{
    T
Query<T>() where T : IRequest;
    T
Dispatch<T>() where T : IDispatch;
}

public interface IRequest
{
}

public interface IDispatch
{

}

public interface IRequestFor<TResult> : IRequest
{
   
TResult Reply();
}

public interface IRequestFor<TParameters, TResult> : IRequest
{
   
TResult With(TParameters parameters);
}

public interface IDispatchFor<TParameters> : IDispatch
{
   
void Using(TParameters parameters);
}

Service Bus Implementation

public class ServiceBus : IServiceBus
{
   
private readonly IKernel kernel;

   
public ServiceBus(IKernel kernel) {
       
this.kernel = kernel;
   
}

   
/// <summary>
   
/// Request a query behavior that may be given parameters to yield a result.
   
/// </summary>
   
/// <typeparam name="T">The type of query to request.</typeparam>
   
/// <returns></returns>
   
public T Query<T>() where T : IRequest
   
{
       
// return a simple injected instance of the query.
       
return kernel.Get<T>();
   
}

   
/// <summary>
   
/// Request a dispatch handler for a given query that may be given parameters to send.
   
/// </summary>
   
/// <typeparam name="T">The type of handler to dispatch.</typeparam>
   
/// <returns></returns>
   
public T Dispatch<T>() where T : IDispatch
   
{
       
// return a simple injected instance of the dispatcher.
       
return kernel.Get<T>();
   
}
}

Service Bus Dependency Injection Wiring (Ninject)

Bind<IServiceBus>()
               
.To<ServiceBus>()
               
.InSingletonScope();

Complete Unit Test

    [TestMethod]
   
public void Login_Post_ReturnsRedirectOnSuccess()
   
{
       
// Inject
       
var service = Mock.Create<IServiceBus>();
       
var authenticationService = Mock.Create<System.Web.Security.IFormsAuthenticationService>();

       
// Arrange
       
var controller = new Web.Controllers.MembershipController(
            service
, authenticationService
       
);

       
var httpContext = Mock.Create<HttpContextBase>();

       
// Arrange
       
var requestContext = new RequestContext(
           
new MockHttpContext(),
           
new RouteData());

        controller
.Url = new UrlHelper(
            requestContext
       
);

       
// Model
       
var model = new Web.Models.Membership.Login
       
{
           
Email = "acceptible@email.com",
           
Password = "acceptiblePassword",
           
RememberMe = true
       
};

       
// Arrange
       
Mock.Arrange(() => service.Query<Membership.Messages.ValidateMember>().With(model))
           
.Returns(true);

       
// Act
       
var result = controller.Login(model, "/Home/");

       
// Assert
       
Assert.IsInstanceOfType(result, typeof(RedirectResult));
   
}

Actual Query Method

public class ValidateMember : IRequestFor<IValidateMemberParameters, bool>
{
   
private readonly ISession session;

   
public ValidateMember(ISession session) {
       
this.session = session;
   
}

   
public bool With(IValidateMemberParameters model)
   
{
       
if (String.IsNullOrEmpty(model.Email)) throw new ArgumentException("Value cannot be null or empty.", "email");
       
if (String.IsNullOrEmpty(model.Password)) throw new ArgumentException("Value cannot be null or empty.", "password");

       
// determine if the credentials entered can be matched in the database.
       
var member = session.Query<Member>()
           
.Where(context => context.Email == model.Email)
           
.Take(1).SingleOrDefault();

       
// if a member was discovered, verify their password credentials
       
if( member != null )
           
return System.Security.Cryptography.Hashing.VerifyHash(model.Password, "SHA512", member.Password);

       
// if we reached this point, the password could not be properly matched and there was an error.
       
return false;
   
}
}

8 Answers, 1 is accepted

Sort by
0
Ricky
Telerik team
answered on 21 Apr 2011, 05:26 PM
Hi Stacey,
Thanks again for your detailed post. However, I took your sample and wrote it in a bit simplified way to better replicate the issue.

Here, I think that you need to include an additional line that sets service.Query<ValidateMember> to return specific ValidateMember instance and then mock its member With(model) for your expected value:

var session = Mock.Create<ISession>();
var validateMember = Mock.Create<ValidateMember>(session);
Mock.Arrange(() => service.Query<ValidateMember>()).Returns(validateMember);
 
Mock.Arrange(()=> validateMember.With(model)).Returns(true);

You are basically getting the null reference exception during service.Query<ValidateMember> call. Since, nested mocking works with method calls or properties returning instance type with default constructor. Therefore, it is returning null in this regard.  But this is a good issue to ponder and it will be great to include support for nested mocking of instance type with non default constructor as well. I have further created an PITS item that you can follow for progress:

http://www.telerik.com/support/pits.aspx#/public/justmock/5664


Finally, hope that the above solution works for you and i am also attaching my sample project to let you have a look. Please write us back if you still having problems.


Kind Regards,
Mehfuz
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Stacey
Top achievements
Rank 1
answered on 21 Apr 2011, 06:23 PM
This still does not work. The ISession throws an exception when it gets called in the 'With' method. I've even tried mocking the session.Query<Member>() method call and it does not work.
0
Ricky
Telerik team
answered on 22 Apr 2011, 11:59 AM
Hi Stacey,

Thanks again for your reply. However, can you please try the sample i have sent and let me know what exception you are having for that? This will help me assist you with a better solution.  Here to note that i am using the latest JustMock build from NuGet (you can find the same from downloads).

Sorry for the inconvenience.

Kind regards,
Mehfuz
the Telerik team

Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Stacey
Top achievements
Rank 1
answered on 22 Apr 2011, 03:06 PM
Your example project works fine, however it does not appropriately demonstrate the problem I am having. 

In the ValidateMember class, your 'With' method does not call upon ISession. Mine looks like this ..

public bool With(IValidateMemberParameters model)
   
{
       
if (String.IsNullOrEmpty(model.Email)) throw new ArgumentException("Value cannot be null or empty.", "email");
       
if (String.IsNullOrEmpty(model.Password)) throw new ArgumentException("Value cannot be null or empty.", "password");

       
// determine if the credentials entered can be matched in the database.
       
var member = session.Query<Member>()
           
.Where(context => context.Email == model.Email)
           
.Take(1).SingleOrDefault();

       
// if a member was discovered, verify their password credentials
       
if( member != null )
           
return System.Security.Cryptography.Hashing.VerifyHash(model.Password, "SHA512", member.Password);

       
// if we reached this point, the password could not be properly matched and there was an error.
       
return false;
   
}

when session.Query<Member>().Where(context => context.Email == model.Email).Take(1).SingleOrDefault(); is called, I get a NullReferenceException

This occurs even when using your proposed method of mocking ISession through the ValidateMember constructor. 

It is my understanding that the actual method should never be called, but for whatever reason it is. Your example project works fine, but it does not have the same code. The ISession is from nHibernate

I have a lot more code of my project posted here : http://stackoverflow.com/questions/5663435/mocking-a-simple-service-bus-in-asp-net-mvc
But I can add it here for your convenience.

Basically I am down to the problem of ... if I take the session.Query<Member> out of my actual code, the test will pass, but if I leave my code intact, the test will always fail.

Login Controller Action

    [ValidateAntiForgeryToken] 
   
[HttpPost]
   
public ActionResult Login(Web.Models.Membership.Login model, string returnUrl)
   
{
       
if (ModelState.IsValid)
       
{
           
// attempt to validate the user, and if successful, pass their credentials to the
           
// forms authentication provider.
           
if (Bus.Query<ValidateMember>().With(model))
           
{
               
// retrieve the authenticated member so that it can be passed on
               
// to the authentication service, and logging can occur with the
               
// login.
               
Authentication.SignIn(model.Email, model.RememberMe);

               
if (Url.IsLocalUrl(returnUrl))
                   
return Redirect(returnUrl);
               
else
                   
return RedirectToAction("Index", "Home");
           
}
           
else
           
{
               
ModelState.AddModelError("", "The user name or password provided is incorrect.");
           
}
       
}

       
// If we got this far, something failed, redisplay form
       
return View(model);
   
}

Login View Model

public class Login : Membership.Messages.IValidateMemberParameters
{
   
[Required]
   
[DataType(DataType.EmailAddress)]
   
[RegularExpression(@"^[a-z0-9_\+-]+(\.[a-z0-9_\+-]+)*@(?:[a-z0-9-]+){1}(\.[a-z0-9-]+)*\.([a-z]{2,})$", ErrorMessage = "Invalid Email Address")]
   
[Display(Name = "Email Address")]
   
public string Email { get; set; }

   
[Required]
   
[StringLength(32, MinimumLength = 6)]
   
[DataType(DataType.Password)]
   
[RegularExpression(@"^([a-zA-Z0-9@#$%]){6,32}$", ErrorMessage = "Invalid Password. Passwords must be between 6 and 32 characters, may contain any alphanumeric character and the symbols @#$% only.")]
   
[Display(Name = "Password")]
   
public string Password { get; set; }

   
[Display(Name = "Remember me?")]
   
public bool RememberMe { get; set; }
}


0
Stacey
Top achievements
Rank 1
answered on 25 Apr 2011, 02:29 PM
Still the only way I can get a test to pass is to remove my ISession.Query<T> entirely from my real code - - and from my understanding, it isn't good unit testing if you have to change your original code to make it work. I've tried .DoNothing(), .DoInstead(), etc. None of it seems to make any difference... 
0
Ricky
Telerik team
answered on 28 Apr 2011, 11:22 AM
Hi Stacey,
Thanks again for your reply. However, if you are using the free version and as it does not support profiling the following test code will fail:

var session = Mock.Create<ISession>();
var validateMember = Mock.Create<ValidateMember>(session);
Mock.Arrange(() => service.Query<ValidateMember>()).Returns(validateMember);
  
Mock.Arrange(()=> validateMember.With(model)).Returns(true);

In other words, With is a final method and without the profiler JustMock can't intercept the call to return expected "true".  Therefore, it executes the original code block and fails on the ISession.Query<> , since it uses a LINQ query (IQueryable) and expecting a collection (while can  also be mocked using the commercial edition).

But good news is that all your target methods are implemented from its corresponding interface, therefore you could write the test in the following way:

var validateMember = Mock.Create<IRequestFor<IValidateMemberParameters, bool>>();
Mock.Arrange(() => service.Query<IRequestFor<IValidateMemberParameters, bool>>()).Returns(validateMember);
Mock.Arrange(()=> validateMember.With(model)).Returns(true);
 
Assert.IsTrue(service.Query<IRequestFor<IValidateMemberParameters, bool>>().With(model));


This will make validateMemeber.With(model) to return the expected value and pass the test. Hope this solves your issue.


Should you have anymore questions , please don't hesitate to write us back.

Kind Regards,
Mehfuz
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Stacey
Top achievements
Rank 1
answered on 10 Jun 2011, 02:03 AM
I have downloaded a trial of the full version, and the query still does not work. Do I need to do something 'special' to enable this 'profiling' feature you speak of?
0
Ricky
Telerik team
answered on 15 Jun 2011, 09:04 AM
Hi Stacey,
Thanks for the reply. If you have checked "Integrate with visual studio" option during installation then it should have automatically registered the profiler for you. In order to check if the profiler is working correctly, you can check this line in your test method:


Assert.IsTrue(Mock.IsProfilerEnabled);

Optionally, please make sure that your trial has not expired.

Kind Regards,
Mehfuz
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
Tags
General Discussions
Asked by
Stacey
Top achievements
Rank 1
Answers by
Ricky
Telerik team
Stacey
Top achievements
Rank 1
Share this question
or