I have a Service Stack Service that uses the following code
public MyResponse Get(MyRequest request){
var authSession = GetSession();
var tokens = authSession.GetOAuthTokens("somekey");
var jwt = JwtPayload.Deserialize(tokens.AccessTokenSecret);
var clientId = jwt.Claims.First(x => x.Type == "client_id").Value;
//...
I am creating a unit test around this service function, but it seems that GetOAuthTokens is a static extension method which cannot be Mocked by Moq (or by any Mocking framework for that example).
This is my current test function
[Test]
public void Get_ShouldReturnListOfApplications()
{
using (new BasicAppHost(typeof(ApplicationService).Assembly).Init())
{
var req = new MockHttpRequest();
req.Items[SessionFeature.RequestItemsSessionKey] =
new AuthUserSession
{
UserName = "Mocked",
RequestTokenSecret="test",
ProviderOAuthAccess = new System.Collections.Generic.List<IAuthTokens>() { MockTokens.Object }
};
using (var service = HostContext.ResolveService<ApplicationService>(req))
{
service.Client = MockServiceClient.Object;
service.Settings = MockSettingsProvider.Object;
var response = service.Get(TestApplicationRequest);
}
}
}
Is there anyway to mock the results of that "GetOAuthTokens" function?
Here's the implementation of the GetOAuthTokens() extension method:
public static IAuthTokens GetOAuthTokens(this IAuthSession session, string provider)
{
foreach (var tokens in session.ProviderOAuthAccess)
{
if (string.Compare(tokens.Provider, provider, StringComparison.InvariantCultureIgnoreCase) == 0)
return tokens;
}
return null;
}
So it should be returning all tokens in session.ProviderOAuthAccess where the provider == "somekey"
Related
I'm new at asp.net and moq and xunit test, I want to create a test case for my asp.net project. I have the service file that is CRUD action,
I have been written for Create Method it's Ok but at Get, GetAll, Delete, Put method I don't know what should I setup
public void Test1()
{
var tutorRequest = new Mock<ITutorRequestService>();
//What should i set up for Get and GetAll
tutorRequest.Setup(r => r.Get(It.IsAny<int>(), It.IsAny<string?>()))
.ReturnsAsync(() => new Entity());
var logger = new Mock<ILogger<TutorRequestsController>>();
var controller = new TutorRequestsController(tutorRequest.Object, logger.Object);
var rs = controller.Get(1, "");
Assert.NotNull(rs);
}
My service class
public async Task<TutorRequest> Create(TutorRequestModel tutor)
{
var entity = _mapper.Map<TutorRequest>(tutor);
CheckDataNotNull("entity", entity);
entity = await _repository.Add(entity);
return _mapper.Map<TutorRequest>(entity);
}
public async Task<TutorRequest> Delete(int tutorId)
{
var entity = await _repository.Delete(tutorId);
return _mapper.Map<TutorRequest>(entity);
}
public async Task<Entity> Get(int id, string? fields)
{
var entity = await _repository.Get(id);
CheckDataNotNull("tutor request", entity);
var shaped = _dataShaper.ShapeData(entity, fields);
CheckDataNotNull("shaped", shaped);
return _mapper.Map<Entity>(shaped);
}
public PageList<Entity> GetAll(TutorRequestParams param)
{
var listAll = _repository.GetAll();
CheckDataNotNull("shaped", listAll);
Search(ref listAll, param);
var sortedStudent = _sortHelper.ApplySort(listAll, param.OrderBy);
var shapedOwners = _dataShaper.ShapeData(sortedStudent, param.Fields);
return PageList<Entity>.ToPageList(shapedOwners, param.PageNume, param.PageSize);
}
public async Task<TutorRequest> Update(TutorRequestModel tutor)
{
var entity = _mapper.Map<TutorRequest>(tutor);
entity = await _repository.Update(entity);
return _mapper.Map<TutorRequest>(entity);
}
If you're expecting the GetAll method to return something, you need to set up the call for the mocked service.
mockRepo.Setup(r => r.GetAll(It.IsAny<TutorRequestParams>())).Returns(
new PagedList<ExpandoObject>
{
// result you're expecting
}
);
I have write a test case for Get method that similar to your example, you can check it out
public void Test_Get_Method()
{
var questService = new Mock<IQuestService>();
questService.Setup(x => x.Get(It.IsAny<Guid>()))
.ReturnsAsync(new QuestResponseModel()
{
Id = Guid.Empty,
Title = "Test",
Description = "TestDescription",
Price = 100,
Status = "Pending",
AvailableTime = DateTime.Now,
EstimatedTime = "120",
QuestTypeId = 1
});
var mapperMock = new Mock<IMapper>();
var controller = new QuestController(questService.Object);
var result = controller.Get(Guid.Empty);
Assert.Equal("TestDescription", result.Result.Data.Description);
}
I have an ASP.NET WEB API project that using the repository and unit of work patterns along with UnityContainer. I registered all my repositories and unit of work classes with PerRequestLifeTimeManager and everything is working great. Every client request to server work in a single transaction separated from other client requests. After the request is returned to the client, the DbContext is disposed.
My problem begins when I need to to perform heavy actions after I return a response to the client. The DbContext is disposed and I can't request the Unity Container for a new instance (Since no HttpContext exist ).
I read this post PerRequestLifetimeManager can only be used in the context of an HTTP request
And tried to implement 2 different UnityContainer
WebContainer - Register all classes using PerRequestLifeTimeManager
CorContainer - Register all classes using PerResolveLifetimeManager
I thought I solved the problem but I noticed that each repository I request from the CoreContainer using a different DbContext. Also the UnitOfWork has a different DbContext. This off course causing many error in my code.
This is the code I'm using to register my entities in UnityContainer. I have 2 Databases so some of the repository using the DbContext of the first database, and the other using the second DbContext. My UnitOfWork using both DbContexts
public static class UnityConfig
{
#region Unity Container
private static Lazy<IUnityContainer> perRequestContainer =
new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterCommonTypes<PerRequestLifetimeManager>(container);
return container;
});
private static Lazy<IUnityContainer> perResolveContainer =
new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterCommonTypes<PerResolveLifetimeManager>(container);
//CNSDeployerService can
container.RegisterType<ICNSDeployerService, CNSDeployerService>(new PerResolveLifetimeManager());
return container;
});
public static IUnityContainer WebContainer => perRequestContainer.Value;
public static IUnityContainer CoreContainer => perResolveContainer.Value;
#endregion
/// <summary>
/// Please notice this configuration exist only inside the scope of a single request
/// See UnityWebApiActivator
/// </summary>
/// <param name="container"></param>
public static void RegisterCommonTypes<T>(IUnityContainer container) where T: LifetimeManager
{
container.RegisterType<ApplicationDbContext, ApplicationDbContext>(Activator.CreateInstance<T>());
container.RegisterType<DbContext, ApplicationDbContext>(Activator.CreateInstance<T>());
container.RegisterType<capNsubContext, capNsubContext>(Activator.CreateInstance<T>());
container.RegisterType<IDataContextAsync, capNsubContext>("capNsubContext", Activator.CreateInstance<T>());
container.RegisterType<IDataContextAsync, LinetServerContext>("linnetDataContext", Activator.CreateInstance<T>());
container.RegisterType<IRepository<Entity>, Repository<Entity>>(Activator.CreateInstance<T>());
container.RegisterType<IRepositoryAsync<VideoTargetLanguage>, Repository<VideoTargetLanguage>>(Activator.CreateInstance<T>());
//Unity understand array by defaults so we just need to map it to IEnumerable
container.RegisterType<IEnumerable<IDataContextAsync>, IDataContextAsync[]>();
container.RegisterType<IUnitOfWorkAsync, UnitOfWork>(Activator.CreateInstance<T>());
container.RegisterType<UserManager<ApplicationUser>>(Activator.CreateInstance<T>());
container.RegisterType<RoleManager<IdentityRole>>(Activator.CreateInstance<T>());
container.RegisterType<AccountController>(Activator.CreateInstance<T>());
container.RegisterType<IUserStore<ApplicationUser>, UserStore<ApplicationUser>>(Activator.CreateInstance<T>());
container.RegisterType<IOrderService, OrderService>(Activator.CreateInstance<T>());
container.RegisterType<IFFMpegService, FFMpegService>(Activator.CreateInstance<T>());
container.RegisterType<IVideoService, VideoService>(Activator.CreateInstance<T>());
container.RegisterType<IOrderItemService, OrderItemService>(Activator.CreateInstance<T>());
container.RegisterType<ILanguageService, LanguageService>(Activator.CreateInstance<T>());
container.RegisterType<IUserService, UserService>(Activator.CreateInstance<T>());
container.RegisterType<ICNSCaptionsService, CNSCaptionsService>(Activator.CreateInstance<T>());
container.RegisterType<ICNSTranslationsService, CNSTranslationsService>(Activator.CreateInstance<T>());
container.RegisterType<ICNSCapMoviesService, CNSMovieService>(Activator.CreateInstance<T>());
container.RegisterType<HttpClient, HttpClient>(Activator.CreateInstance<T>());
container.RegisterType<SimpleRefreshTokenProvider, SimpleRefreshTokenProvider>(Activator.CreateInstance<T>());
var capNsubEntityTypes = GetEntityFrameworkEntityTypesByContext<capNsubContext>();
var linnetEntityTypes = GetEntityFrameworkEntityTypesByContext<LinetServerContext>();
RegisterEntitiesRepostiories<T>(container, capNsubEntityTypes, "capNsubContext");
RegisterEntitiesRepostiories<T>(container, linnetEntityTypes, "linnetDataContext");
}
private static void RegisterEntitiesRepostiories<T>(IUnityContainer container, IEnumerable<Type> entities, string contextName)
where T:LifetimeManager
{
var iGenericRepositoryTypes = new[] { typeof(IRepositoryAsync<>), typeof(IRepository<>) };
foreach (var iGenericRepositoryType in iGenericRepositoryTypes)
{
foreach (var entityType in entities)
{
var iSpecificRepositoryType = iGenericRepositoryType.MakeGenericType(entityType);
var genericRepositoryType = typeof(Repository<>);
var specificRepositoryType = genericRepositoryType.MakeGenericType(entityType);
container.RegisterType(iSpecificRepositoryType, Activator.CreateInstance<T>(), new InjectionFactory(c =>
{
return Activator.CreateInstance(specificRepositoryType, c.Resolve<IDataContextAsync>(contextName), c.Resolve<IUnitOfWorkAsync>());
}));
}
}
}
private static IEnumerable<Type> GetEntityFrameworkEntityTypesByContext<T>() where T : DataContext
{
var capNsubContextType = typeof(T);
var capNsubDataAssembly = Assembly.GetAssembly(capNsubContextType);
var ef6EntityType = typeof(Repository.Pattern.Ef6.Entity);
return capNsubDataAssembly.GetTypes()
.Where(t => String.Equals(t.Namespace, capNsubContextType.Namespace, StringComparison.Ordinal) &&
t.IsSubclassOf(ef6EntityType));
}
}
[System.Web.Http.Authorize(Roles = "admin")]
[System.Web.Http.RoutePrefix("api/job")]
public class JobController : BaseApiController {
[System.Web.Http.Route("Create", Name = "Create")]
[System.Web.Http.HttpPost]
public IHttpActionResult Create(JobBindingModel createJobModal)
{
//We have to use the CoreContainer since cnsDeployer scope runs outside of the request
var cnsDeployer = UnityConfig.CoreContainer.Resolve<ICNSDeployerService>();
if (!ModelState.IsValid)
{
return BadRequest();
}
try
{
//This runs in the backround after we return the response to client
cnsDeployer.Deploy(createJobModal.ItemIds);
return Ok();
}
catch(Exception err)
{
return InternalServerError(err);
}
}
}
public class CNSDeployerService : ICNSDeployerService
{
private readonly IOrderItemService orderItemService;
private readonly ICNSCapMoviesService cnsMoviesService;
private readonly ICNSTranslationsService cnsTranslationsService;
private readonly IFFMpegService ffMpegService;
private readonly IUnitOfWorkAsync unitOfWorkAsync;
private readonly IVideoService videoService;
public CNSDeployerService(IOrderItemService orderItemService,
ICNSCapMoviesService cnsCapMoviesService,
ICNSTranslationsService cnsTranslationsService,
IFFMpegService ffMpegService,
IUnitOfWorkAsync unitOfWorkAsync,
IVideoService videoService)
{
this.orderItemService = orderItemService;
this.cnsMoviesService = cnsCapMoviesService;
this.cnsTranslationsService = cnsTranslationsService;
this.ffMpegService = ffMpegService;
this.unitOfWorkAsync = unitOfWorkAsync;
this.videoService = videoService;
}
public void Deploy(IEnumerable<Guid> orderItemIds)
{
try
{
InnerDeploy(orderItemIds);
}
catch
{
unitOfWorkAsync.Dispose();
}
}
private void InnerDeploy(IEnumerable<Guid> orderItemIds)
{
var orderItems = orderItemService.Queryable()
.Where(orderItem => orderItemIds.Any(itemId => orderItem.Id == itemId)
&& orderItem.IsInProcessQueue == false
&& !orderItem.TranslationId.HasValue)
.ToList();
if (orderItems.Count == 0)
{
unitOfWorkAsync.Dispose();
throw new ArgumentNullException("No valid orders was provided");
}
foreach ( var orderItem in orderItems)
{
orderItem.IsInProcessQueue = true;
orderItemService.Update(orderItem);
}
unitOfWorkAsync.SaveChanges();
var translationId = Guid.NewGuid();
var movieId = Guid.NewGuid();
var connectedMoviePath = cnsMoviesService.GetMoviePath(movieId);
var videosUrlList = orderItems
.Select(orderItem => orderItem.VideosTable.VideoUrl)
.ToList();
//Don't await on this task since we want concat to continue after request is returned
Task.Run(async () =>
{
try
{
await ffMpegService.ConcatVideos(videosUrlList, connectedMoviePath);
VideoUtils.CreateVideoImageAndReturnPath(connectedMoviePath);
var videosTotalDuration = videoService.GetVideosTotalDuration(orderItemIds);
var durationInSeconds = Convert.ToInt32((int)(videosTotalDuration / 1000));
await cnsMoviesService.CreateMovieRecordAsync(movieId, durationInSeconds);
await cnsTranslationsService.CreateTranslationRecordAsync(movieId, translationId, language: 1);
var index = 0;
foreach (var orderItem in orderItems)
{
orderItem.TranslationId = translationId;
orderItem.TranslationIndex = index++;
orderItem.IsInProcessQueue = false;
orderItemService.Update(orderItem);
}
await unitOfWorkAsync.SaveChangesAsync();
}
catch (Exception err)
{
//TODO: Handle error
}
finally
{
//Dispose db context
unitOfWorkAsync.Dispose();
}
});
}
}
I am trying to write unit test case using MsTest for custom filter which has the logic to validate the Antiforgerytoken for POST method in ASP.NET WEB API 2 project.
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
public class ValidateJsonAntiForgeryTokenAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
try
{
string cookieToken = null;
string formToken = null;
if (actionContext.Request.IsAjaxRequest())
{
IEnumerable<string> tokenHeaders;
if (actionContext.Request.Headers.TryGetValues("__RequestVerificationToken", out tokenHeaders))
{
string[] tokens = tokenHeaders.First().Split(':');
if (tokens.Length == 2)
{
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
}
}
if (cookieToken != null && formToken !=null)
{
AntiForgery.Validate(cookieToken, formToken);
}
else
{
AntiForgery.Validate();
}
}
}
catch (Exception ex)
{
ErrorSignal.FromCurrentContext().Raise(ex);
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Forbidden);
}
}
}
In the below code IsAjaxRequest is an extension method
public static class HttpRequestMessageExtensions
{
public static bool IsAjaxRequest(this HttpRequestMessage request)
{
IEnumerable<string> headers;
if (request.Headers.TryGetValues("X-Requested-With", out headers))
{
var header = headers.FirstOrDefault();
if (!string.IsNullOrWhiteSpace(header))
{
return header.ToLowerInvariant() == "xmlhttprequest";
}
}
return false;
}
}
Here my issue how to mock the IsAjaxRequest and how to pass actionContext parameter to the OnActionExecuting method.
Can anyone help me to provide some code samples regarding this?
IsAjaxRequest is an extension method which mean it is a static method.
You shouldn't mock static methods. You should test it as a part of your method under test behavior.
The following example shows how to test invalid request:(I removed the :: ErrorSignal.FromCurrentContext().Raise(ex); since I didn't know which assembly to add... so add the missing assert\s in your test...)
[TestMethod]
public void TestMethod1()
{
var target = new ValidateJsonAntiForgeryTokenAttribute();
var requestMessage = new HttpRequestMessage();
requestMessage.Headers.Add("X-Requested-With", new[] {"xmlhttprequest"});
var fakeDescriptor = new Mock<HttpActionDescriptor>();
var controllerContext = new HttpControllerContext {Request = requestMessage};
var context = new HttpActionContext(controllerContext, fakeDescriptor.Object);
target.OnActionExecuting(context);
Assert.AreEqual(HttpStatusCode.Forbidden, actionContext.Response.StatusCode);
}
You can mock the static method if you really need to. There's my example of the test with Typemock isolator:
[TestMethod, Isolated]
public void TestValidate()
{
//How to fake action context to further passing it as a parameter:
var fake = Isolate.Fake.AllInstances<HttpActionContext>();
Isolate.Fake.StaticMethods<HttpActionContext>();
//How to mock IsAjaxRequset:
var request = new HttpRequestMessage();
Isolate.WhenCalled(() => request.IsAjaxRequest()).WillReturn(true);
//Arrange:
ValidateJsonAntiForgeryTokenAttribute target = new ValidateJsonAntiForgeryTokenAttribute();
Isolate.WhenCalled(() => AntiForgery.Validate()).IgnoreCall();
//Act:
target.OnActionExecuting(fake);
//Assert:
Isolate.Verify.WasCalledWithAnyArguments(() => AntiForgery.Validate());
}
I have a service that requires a UserName to be passed in during construction. The UserName will come from the current web request either cookie or query string.
builder.Register((c,p) => {
var accessService = c.Resolve<IAccessService>();
var access = accessService.GetBySite(Request.QueryString["username"]);
return new JsonWebRequest(access.Site, access.Token);
}).InstancePerRequest();
I've tried to register as above although I receive this error message
Request is not available in this context
You should use HttpContext.Current to access the information from the active context.
builder.Register((c,p) => {
var accessService = c.Resolve<IAccessService>();
var request = HttpContext.Current.Request;
var access = accessService.GetBySite(request.QueryString["username"]);
return new JsonWebRequest(access.Site, access.Token);
}).InstancePerRequest();
Another way of doing it is by using the AutofacWebTypesModule which will import registration for HttpRequestBase. This module is available using the Autofac MVC5 nuget package
builder.RegisterModule<AutofacWebTypesModule>();
builder.Register((c,p) => {
var accessService = c.Resolve<IAccessService>();
var request = c.Resolve<HttpRequestBase>();
var access = accessService.GetBySite(request.QueryString["username"]);
return new JsonWebRequest(access.Site, access.Token);
}).InstancePerRequest();
By the way, for testing purpose and more flexibility I would recommend you to create a IUserNameProvider interface that will get you a username
public interface IUserNameProvider
{
String UserName { get; }
}
public class QueryStringUserNameProvider
{
public QueryStringUserNameProvider(HttpRequestBase request)
{
this._request = request;
}
private readonly HttpRequestBase _request;
public String UserName
{
get
{
return this._request.QueryString["UserName"];
}
}
}
You can use it like this :
builder.RegisterModule<AutofacWebTypesModule>();
builder.RegisterType<QueryStringUserNameProvider>()
.As<IUserNameProvider>()
.InstancePerRequest();
builder.Register((c,p) => {
var accessService = c.Resolve<IAccessService>();
var userNameProvider = c.Resolve<IUserNameProvider>();
var access = accessService.GetBySite(userNameProvider.UserName);
return new JsonWebRequest(access.Site, access.Token);
}).InstancePerRequest();
In our MVC4 application with Entity Framework 4.0 based on the Music Store Tutorial we are using Moq to mock the DbContext and unit test are logic. One of our methods proves difficult to test though since it makes use of HttpContext or HttpContextBase. One example method looks like this:
public static ShoppingCart GetCart(HttpContextBase context)
{
var cart = new ShoppingCart();
cart.ShoppingCartId = cart.GetCartId(context);
return cart;
}
The only property collected from HttpContextBase is the [CartSessionKey] as can be seen here:
public string GetCartId(HttpContextBase context)
{
if (context.Session[CartSessionKey] == null)
{
if (!string.IsNullOrWhiteSpace(context.User.Identity.Name))
{
context.Session[CartSessionKey] =
context.User.Identity.Name;
}
else
{
// Generate a new random GUID using System.Guid class
Guid tempCartId = Guid.NewGuid();
// Send tempCartId back to client as a cookie
context.Session[CartSessionKey] = tempCartId.ToString();
}
}
return context.Session[CartSessionKey].ToString();
}
We have heard horror stories that HttpContext is a very complex class and that if you print it you have enough paper to circle the earth eight times.
Nevertheless we want to mock it. The question is how. The properties that we want to mock are the [CartSessionKey], and the property that come from the context as contest.User.Identity.Name.
We suspect we need to use something like this:
var mockData = new Mock<FakeContext>();
mockData.Setup(m => m.Orders).Returns(memoryOrderItems);
mockData.Setup(m => m.Carts).Returns(memoryCartItems);
Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
Mock<HttpRequestBase> mockHttpRequest = new Mock<HttpRequestBase>();
mockHttpRequest.Setup(x => x.CartSessionKey).Returns(1);
mockHttpContext.Setup(x => x.Request).Returns(mockHttpRequest.Object);
but we cannot find how to specifically implement this so we do not get any errors on methods that use context.Session[CartSessionKey] or context.User.Identity.Name.
We hope someone can help us out.
/edit
When we do this:
var memoryUserItems = new FakeDbSet<User>()
{
new User { Email = "test#test.de",
FullName = "Test Person",
isAvailable = true,
Name = "WHat"
},
new User { Email = "test2#test.de",
FullName = "Test Person 2",
isAvailable = true,
Name = "WHat 2"
}
};
(...) Other memory...Items
And then this:
// Create mock units of work
var mockData = new Mock<FakeContext>();
mockData.Setup(m => m.Orders).Returns(memoryOrderItems);
mockData.Setup(m => m.Carts).Returns(memoryCartItems);
mockData.Setup(m => m.Users).Returns(memoryUserItems);
var principalMock = new Mock<IPrincipal>();
var identityMock = new Mock<IIdentity>();
var userMock =
identityMock.Setup(x => x.Name).Returns("Test!");
identityMock.Setup(x => x.IsAuthenticated).Returns(true); // optional ;)
mockData.Setup(x => x.Identity).Returns(identityMock.Object);
var httpReqBase = new Mock<HttpRequestBase>(); // this is useful if you want to test Ajax request checks or cookies in the controller.
var httpContextBase = new Mock<HttpContextBase>();
httpContextBase.Setup(x => x.User).Returns(principalMock.Object);
httpContextBase.Setup(x => x.Session[It.IsAny<string>()]).Returns(1); //Here is the session indexer. You can swap 'any' string for specific string.
httpContextBase.Setup(x => x.Request).Returns(httpReqBase.Object);
We get the error that:
Error 3 'project.Models.FakeContext' does
not contain a definition for 'Identity' and no extension method
'Identity' accepting a first argument of type
'project.Models.FakeContext' could be found
(are you missing a using directive or an assembly
reference?)
/ edit2
To make it more clear. The actual method I am testing is the following:
public ActionResult Complete(int id)
{
// Make sure that user is currentuser and otherwise bring user to our Thief page
if (id != db.GetCurrentUserId())
{
return View("Thief");
}
var cart = ShoppingCart.GetCart(this.HttpContext);
var currentDate = DateTime.Today;
var viewModel = new ShoppingCartViewModel
{
CartItems = cart.GetCartItems(),
CartTotal = cart.GetTotal(),
ProductItems = db.Products.ToList()
};
if (viewModel.CartItems.Count() == 0)
{
return View("Empty");
}
// Try to write cart to order table
try
{
foreach (var item in viewModel.CartItems)
{
ProcessOrder(item, id, currentDate);
}
// after this we empty the shopping cart
cart.EmptyCart();
return View();
}
catch
{
// Invalid - display error page
return View("Error");
}
}
As can be seen the var cart = ShoppingCart.GetCart(this.HttpContext); uses this.HttpContext. In the test I just do controller.Complete(1). I cannot pass a new HttpContext to the controller I guess?
/ edit 3
While using the code below with the mocks I get the following message:
Test Name: TestCheckoutCompleteShouldWithEmptyCart
Test FullName: Controllers.CheckoutControllerTest.TestCheckoutCompleteShouldWithEmptyCart
Test Source: Controllers\CheckoutControllerTest.cs : line 141
Test Outcome: Failed
Test Duration: 0:00:00.0158591
Result Message:
Test method Controllers.CheckoutControllerTest.TestCheckoutCompleteShouldWithEmptyCart threw exception:
System.NullReferenceException: Object reference not set to an instance of an object.
Result StackTrace:
at Models\ShoppingCart.cs:line 170
at \Models\ShoppingCart.cs:line 20
at \Controllers\CheckoutController.cs:line 48
at Controllers\CheckoutControllerTest.cs:line 143
OK, here it goes. The following works in MVC5 with AD, I'm not sure if it's fully backwards compatible, you'll have to check.
var principalMock = new Mock<IPrincipal>();
var identityMock = new Mock<IIdentity>();
identityMock.Setup(x => x.Name).Returns("Test!");
identityMock.Setup(x => x.IsAuthenticated).Returns(true); // optional ;)
userMock.Setup(x => x.Identity).Returns(identityMock.Object);
var httpReqBase = new Mock<HttpRequestBase>(); // this is useful if you want to test Ajax request checks or cookies in the controller.
var httpContextBase = new Mock<HttpContextBase>();
httpContextBase.Setup(x => x.User).Returns(principalMock.Object);
httpContextBase.Setup(x => x.Session[It.IsAny<string>()]).Returns(1); //Here is the session indexer. You can swap 'any' string for specific string.
httpContextBase.Setup(x => x.Request).Returns(httpReqBase.Object);
This would help you to write a proper Unit Test using Moq.
[TestClass]
public class SutTest
{
[TestMethod]
public void GetCartId_WhenUserNameIsNotNull_SessionContainsUserName()
{
var httpContextStub = new Mock<HttpContextBase>();
var httpSessionStub = new Mock<ISessionSettings>();
httpSessionStub.Setup(x => x.Get<string>(It.IsAny<string>())).Returns(() => null);
httpSessionStub.SetupSequence(x => x.Get<string>(It.IsAny<string>()))
.Returns(null)
.Returns("FakeName");
var httpUserStub = new Mock<IPrincipal>();
var httpIdenttyStub = new Mock<IIdentity>();
httpUserStub.SetupGet(x => x.Identity).Returns(httpIdenttyStub.Object);
httpIdenttyStub.SetupGet(x => x.Name).Returns("FakeName");
httpContextStub.Setup(x => x.User).Returns(httpUserStub.Object);
var sut = new Sut(httpSessionStub.Object);
var result = sut.GetCartId(httpContextStub.Object);
Assert.AreEqual("FakeName",result );
}
}
Check the SetupSequence method which gives you find Control over different values being return on he same stubbed call.
Also important to decouple your session from HttpContext as you can always run into issues.
public class SessionSettings : ISessionSettings
{
private readonly HttpSessionStateBase _session;
public SessionSettings(HttpSessionStateBase session)
{
_session = session;
}
public T Get<T>(string key)
{
return (T)_session[key];
}
public void Set<T>(string key, T value)
{
_session[key] = value;
}
}
public interface ISessionSettings
{
T Get<T>(string key);
void Set<T>(string key, T value);
}
public class Sut
{
private ISessionSettings _sessionSettings;
public Sut(ISessionSettings sessionSettings)
{
_sessionSettings = sessionSettings;
}
public string GetCartId(HttpContextBase context)
{
if (_sessionSettings.Get<string>(CartSessionKey) == null)
{
if (!string.IsNullOrWhiteSpace(context.User.Identity.Name))
{
_sessionSettings.Set<string>(CartSessionKey, context.User.Identity.Name);
}
else
{
// Generate a new random GUID using System.Guid class
Guid tempCartId = Guid.NewGuid();
// Send tempCartId back to client as a cookie
_sessionSettings.Set<string>(CartSessionKey, tempCartId.ToString());
}
}
return _sessionSettings.Get<string>(CartSessionKey);
}
private string CartSessionKey = "key";
}
This way the code is more readable and easier to understand.