i'm creating a shopping website using web forms and MVP pattern in a three layered architecture. i also decided to do the validation and type castings inside of presenter class.for testing framework i use NUnit and for my mocks i use NSubstitude. here is my category model class:
//we're doing Dependency injection here.
public abstract class BaseRepository
{
EntityContext context;
public BaseRepository()
{
context = new EntityContext();
}
public EntityContext Context
{
get { return context; }
}
}
public class CategoryRepository : BaseRepository
{
public int Add(long id, string name)
{
Category cat = new Category();
cat.Id = id;
cat.Name = name;
Context.Category.Add(cat);
Context.SaveChanges();
}
}
here is the category presenter:
public class CategoryPresenter : BasePresenter //has nothing but a dependency property to Logger
{
BaseRepository _model;
IView _view;
public CategoryPresenter(IView view)
{
_model = new CategoryRepository();
_view = view;
}
public void Add()
{
//havn't passed the tests yet since i'm not sure if i'm on the correct path.
//whatever validation, loggin and type casting will go here.
_model.Add(_view.CategoryId, _view.CategoryName);
}
}
and here is the test class for the presenter:
[Test]
public void Add_NullId_ThrowException()
{
_view.CategoryId.Returns(p => null);
_view.CategoryName.Returns(p => "test");
Assert.Throws(typeof(InvalidOperationException), _presenter.Add());
}
[Test]
public void Add_EmptyId_ThrowException()
{
_view.CategoryId.Returns(p => "");
_view.CategoryName.Returns(p => "test");
Assert.Throws(typeof(InvalidOperationException), _presenter.Add());
}
[Test]
public void Add_SpaceOnlyId_ThrowException()
{
_view.CategoryId.Returns(p => " ");
_view.CategoryName.Returns(p => "test");
Assert.Throws(typeof(InvalidOperationException), _presenter.Add());
}
[Test]
public void Add_InvalidLowBoundId_ThrowException()
{
_view.CategoryId.Returns(p => "-1");
_view.CategoryName.Returns(p => "test");
Assert.Throws(typeof(InvalidOperationException), _presenter.Add());
}
[Test]
public void Add_InvalidHighBoundId_ThrowException()
{
_view.CategoryId.Returns(p => long.MaxValue.ToString() + "1");
_view.CategoryName.Returns(p => "test");
Assert.Throws(typeof(InvalidOperationException), _presenter.Add());
}
[Test]
public void Add_EmptyName_ThrowException()
{
_view.CategoryId.Returns(p => "1");
_view.CategoryName.Returns(p => "");
Assert.Throws(typeof(InvalidOperationException), _presenter.Add());
}
[Test]
public void Add_NullName_ThrowException()
{
_view.CategoryId.Returns(p => "1");
_view.CategoryName.Returns(p => null);
Assert.Throws(typeof(InvalidOperationException), _presenter.Add());
}
[Test]
public void Add_SpaceOnlyName_ThrowException()
{
_view.CategoryId.Returns(p => "1");
_view.CategoryName.Returns(p => " ");
Assert.Throws(typeof(InvalidOperationException), _presenter.Add());
}
[Test]
public void Add_NumberOnlyName_ThrowException()
{
_view.CategoryId.Returns(p => "1");
_view.CategoryName.Returns(p => "123");
Assert.Throws(typeof(InvalidOperationException), _presenter.Add());
}
am i testing correctly? i mean is this what the test class should look like? i'm i missing something? is this too much? like "you don't need to test emptiness" or any other issues associated with my tests or code? if you notice anything wrong within my whole code and/or architecture i appreciate it if you correct me. thanks!
Update: IView is inherited by a .aspx page. on code behind i simply call the presenter method from inside of a click event that user is triggered by pressing a button. as for memory i havn't gone that far yet. simply stuck on TDD.
I'd remove the validation logic from the application layer (where presenters live) and extract it into the domain layer (where the repositories live).
Then don't do the validation right there in the presenter, but instead have the presenter call the necessary validators.
For unit tests of the presenter you provide validator mock objects to the presenter and verify that the correct validation methods are called for the data.
So you have to test two things:
1) Test if the presenter calls the validators with the data from the view
2) Test the validators on their own
Tests might look like this:
For the presenter (class CategoryPresenterTests):
[Test]
public void Add_CallsTheValidatorWithDataFromTheView()
{
_viewMock.CategoryId.Returns(p => "id");
_viewMock.CategoryName.Returns(p => "name");
_presenter.Add();
_categoryValidatorMock.Verify(x=>x.Validate("id", "name"), Times.Once);
}
[Test]
public void Add_ForwardsValidationExceptions()
{
_viewMock.CategoryId.Returns(p => "id");
_viewMock.CategoryName.Returns(p => "name");
_categoryValidatorMock.Setup(x=>x.Validate(...)).Throws<ValidationException>();
Assert.Throws<ValidationException>(() => _presenter.Add());
}
Note that we don't care about concrete inputs from the view, only that the validator is called with this exact data from the view and that the result (in this case exception or no exception) is passed back.
For the validator (class CategoryValidatorTests. Basically all your current tests go here):
[Test]
public void NullId_ThrowsException() {
string id = null;
string name = "test";
Assert.Throws<ValidationException>(() => _validator.Validate(id, name));
}
Note that I don't know NSubstitutes syntax so the above is pseudo code.. hope you can decipher it :)
Besides that I wouldn't create the repositories within the presenters, instead inject their interfaces through the constructor (like you did with the IView). Then provide mock objects and, like with the validators, verify that they are called correctly by the presenter.
All of the above should allow you to reuse your validation logic outside of the presenters and it'll take some complexity away from the presenters, allowing them to concentrate more on their actual purpose of mediating between model and view and handling workflows.
Related
Will anything bad happen if there are multiple registrations of services in .net core's DI? For example let's say we have the following code
public void ConfigureServices(IServiceCollection services)
{
//....
services.AddHealthChecks();
//...
}
And in another (extension maybe) class we use services.AddHealthChecks() again. Will this mess the DI's container or not?
Thanks in advance
You can register a service several times without an exception has thrown. The problem is when you register a service several times with different scope. consider following example:
public interface IMyInterface
{
void Print();
}
public class MyInterface : IMyInterface
{
public void Print()
{
}
}
And let's register IMyInterface with two different scopes:
internal class Program
{
private static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
services.AddScoped<IMyInterface, MyInterface>();
services.AddSingleton<IMyInterface, MyInterface>();
var provider = services.BuildServiceProvider();
for (var i = 0; i < 5; i++)
{
var scope = provider.CreateScope();
using (scope)
{
var myInterface = scope.ServiceProvider.GetService<IMyInterface>();
Console.WriteLine(myInterface.GetHashCode());
}
}
}
}
First, register IMyInterface in the following order:
services.AddScoped<IMyInterface, MyInterface>();
services.AddSingleton<IMyInterface, MyInterface>();
As you can see we get a singleton instance of MyInterface and the hashcode is the same.
Now let's change it to this:
services.AddSingleton<IMyInterface, MyInterface>();
services.AddScoped<IMyInterface, MyInterface>();
Now we get the scoped type of MyInterface and the hashcode is different each time. You always get the last registered scope of your type.
Look at AddHealthChecks code:
public static IHealthChecksBuilder AddHealthChecks(this IServiceCollection services)
{
services.TryAddSingleton<HealthCheckService, DefaultHealthCheckService>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, HealthCheckPublisherHostedService>());
return new HealthChecksBuilder(services);
}
By adding services.AddHealthChecks(); more than one time, you just registering HealthCheckService and IHostedService as a singleton services and I think it doesn't affect the health check functionality.
With the following extension, you can find duplicate registration:
public static class ServiceDescription
{
private static List<IGrouping<Type, ServiceDescriptor>> Descriptors;
public static IHostBuilder ConfigureServiceDescriptionCheck(this IHostBuilder hostBuilder)
{
hostBuilder.ConfigureServices(services =>
{
Descriptors = services.Where(i => !i.ServiceType.Assembly.FullName.Contains("Microsoft"))
.GroupBy(p => p.ServiceType)
.Where(x => x.Count() > 1).ToList();
});
return hostBuilder;
}
public static IHost UseServiceDescriptionCheck(this IHost host)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
Descriptors.ForEach(item =>
{
var count = item.Count();
logger.LogWarning("Service of type {Key} has been registered {count} times", item.Key, count);
});
return host;
}
}
And use it in this way:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().UseServiceDescriptionCheck().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureServiceDescriptionCheck();
}
Read this article to get more details.
I have an ASP.NET Core web app and test setup using WebApplicationFactory to test my controller actions. I used RawRabbit before and it was easy enough for me to mock the IBusClient and add it to the DI container as a singleton. Within the WebApplicationFactory<TStartup>.CreateWebHostBuilder() I call this extension method to add my mocked IBusClient instance like so;
/// <summary>
/// Configures the service bus.
/// </summary>
/// <param name="webHostBuilder">The web host builder.</param>
/// <returns>A web host builder.</returns>
public static IWebHostBuilder ConfigureTestServiceBus(this IWebHostBuilder webHostBuilder)
{
webHostBuilder.ConfigureTestServices(services =>
{
services.AddSingleton<IBusClient, MY_MOCK_INSTANCE>
});
return webHostBuilder;
}
But there are gaps in RawRabbit right now that made me decide to move over to MassTransit. However, I am wondering if there's already a better way to register the IBus into my container without mocking it inside my test. Not sure if InMemoryTestFixture, BusTestFixture, or BusTestHarness is the solution to my problem. Not sure how to use them together and what they do.
By the way, in my ASP.NET Core app, I have a reusable extension method setup like the code below to hook me up to RabbitMQ on startup.
/// <summary>
/// Adds the service bus.
/// </summary>
/// <param name="services">The services.</param>
/// <param name="configurator">The configurator.</param>
/// <returns>A service collection.</returns>
public static IServiceCollection AddServiceBus(this IServiceCollection services, Action<IServiceCollectionConfigurator> configurator)
{
var rabbitMqConfig = new ConfigurationBuilder()
.AddJsonFile("/app/configs/service-bus.json", optional: false, reloadOnChange: true)
.Build();
// Setup DI for MassTransit.
services.AddMassTransit(x =>
{
configurator(x);
// Get the json configuration and use it to setup connection to RabbitMQ.
var rabbitMQConfig = rabbitMqConfig.GetSection(ServiceBusOptionsKey).Get<RabbitMQOptions>();
// Add bus to the container.
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.Host(
new Uri(rabbitMQConfig.Host),
hostConfig =>
{
hostConfig.Username(rabbitMQConfig.Username);
hostConfig.Password(rabbitMQConfig.Password);
hostConfig.Heartbeat(rabbitMQConfig.Heartbeat);
});
cfg.ConfigureEndpoints(provider);
// Add Serilog logging.
cfg.UseSerilog();
}));
});
// Add the hosted service that starts and stops the BusControl.
services.AddSingleton<IMessageDataRepository, EncryptedMessageDataRepository>();
services.AddSingleton<IEndpointNameFormatter, EndpointNameFormatter>();
services.AddSingleton<IBus>(provider => provider.GetRequiredService<IBusControl>());
services.AddSingleton<IHostedService, BusHostedService>();
return services;
}
A MassTransit config defined during Startup could be replaced with a new configuration with custom WebApplicationFactory by removing services from MassTransit namespace, e.g.
public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
var massTransitHostedService = services.FirstOrDefault(d => d.ServiceType == typeof(IHostedService) &&
d.ImplementationFactory != null &&
d.ImplementationFactory.Method.ReturnType == typeof(MassTransitHostedService)
);
services.Remove(massTransitHostedService);
var descriptors = services.Where(d =>
d.ServiceType.Namespace.Contains("MassTransit",StringComparison.OrdinalIgnoreCase))
.ToList();
foreach (var d in descriptors)
{
services.Remove(d);
}
services.AddMassTransitInMemoryTestHarness(x =>
{
//add your consumers (again)
});
});
}
}
Then your test could look like
public class TestClass : IClassFixture<CustomApplicationFactory>
{
private readonly CustomApplicationFactoryfactory;
public TestClass(CustomApplicationFactoryfactory)
{
this.factory = factory;
}
[Fact]
public async Task TestName()
{
CancellationToken cancellationToken = new CancellationTokenSource(5000).Token;
var harness = factory.Services.GetRequiredService<InMemoryTestHarness>();
await harness.Start();
var bus = factory.Services.GetRequiredService<IBusControl>();
try
{
await bus.Publish<MessageClass>(...some message...);
bool consumed = await harness.Consumed.Any<MessageClass>(cancellationToken);
//do your asserts
}
finally
{
await harness.Stop();
}
}
}
I ended up creating a method from within my WebApplicationFactory like so;
public void ConfigureTestServiceBus(Action<IServiceCollectionConfigurator> configurator)
{
this._configurator = configurator;
}
Giving me the ability to register test handlers from within my derived integration class constructor;
public Intg_GetCustomers(WebApplicationTestFactory<Startup> factory)
: base(factory)
{
factory.ConfigureTestServiceBus(c =>
{
c.AddConsumer<TestGetProductConsumer>();
});
}
This configurator gets used when I call my extension method to add an InMemory instance of MassTransit
public static IWebHostBuilder ConfigureTestServiceBus(this IWebHostBuilder webHostBuilder, Action<IServiceCollectionConfigurator> configurator)
{
return webHostBuilder
.ConfigureTestServices(services =>
{
// UseInMemoryServiceBus DI for MassTransit.
services.AddMassTransit(c =>
{
configurator?.Invoke(c);
// Add bus to the container.
c.AddBus(provider =>
{
var control = Bus.Factory.CreateUsingInMemory(cfg =>
{
cfg.ConfigureEndpoints(provider);
});
control.Start();
return control;
});
});
services.AddSingleton<IMessageDataRepository, InMemoryMessageDataRepository>();
});
}
You best bet is to use the InMemoryTestHarness, so that you can ensure your message contracts can be serialized, your consumers are configured properly, and that everything works as expected. While some might call this an integration test, it's really just doing a proper test. And it's extremely quick, since it's all in-memory.
You can see a unit test here, but a short example is also shown below.
[TestFixture]
public class When_a_consumer_is_being_tested
{
InMemoryTestHarness _harness;
ConsumerTestHarness<Testsumer> _consumer;
[OneTimeSetUp]
public async Task A_consumer_is_being_tested()
{
_harness = new InMemoryTestHarness();
_consumer = _harness.Consumer<Testsumer>();
await _harness.Start();
await _harness.InputQueueSendEndpoint.Send(new A());
}
[OneTimeTearDown]
public async Task Teardown()
{
await _harness.Stop();
}
[Test]
public void Should_have_called_the_consumer_method()
{
_consumer.Consumed.Select<A>().Any().ShouldBe(true);
}
class Testsumer :
IConsumer<A>
{
public async Task Consume(ConsumeContext<A> context)
{
await context.RespondAsync(new B());
}
}
class A
{
}
class B
{
}
}
In my case (I'm injecting everywhere IPublishEndpoint interface only) I have just simply registered another IPublishEndpoint in ConfigureTestServices method like so:
[TestClass]
public class TastyTests
{
private readonly WebApplicationFactory<Startup> factory;
private readonly InMemoryTestHarness harness = new();
public TastyTests()
{
factory = new WebApplicationFactory<Startup>().WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddSingleton<IPublishEndpoint>(serviceProvider =>
{
return harness.Bus;
});
});
});
}
[TestMethod]
public async Task Test()
{
await harness.Start();
try
{
var client = factory.CreateClient();
const string url = "/endpoint-that-publish-message";
var content = new StringContent("", Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);
(await harness.Published.Any<IMessage>()).Should().BeTrue();
}
finally
{
await harness.Stop();
}
}
}
I'm using FluentValidation with WebAPI in DotNet core 2. I've written tests for the validator successfully, but now I'm trying to mock the validator for my controller. Controller as follows:
[Route("[controller]")]
public class SecurityController : Controller {
private readonly IValidator<AuthenticateRequest> _authenticateRequestValidator;
public SecurityController(IValidator<AuthenticateRequest> authenticateRequestValidator) {
_authenticateRequestValidator = authenticateRequestValidator;
}
[HttpPost]
[AllowAnonymous]
[Route("auth")]
public async Task<IActionResult> AuthenticateAsync([FromBody] AuthenticateRequest req) {
// Validate
var validator = await _authenticateRequestValidator.ValidateAsync(req);
if(!validator.IsValid) {
return BadRequest();
}
// ...snip
}
}
AuthenticateRequest looks like this:
public class AuthenticateRequest {
public string Username { get; set; }
public string Password { get; set; }
}
And the validator is as follows:
public class AuthenticateRequestValidator : AbstractValidator<AuthenticateRequest> {
/// <summary>
/// Provides a validator for <see cref="AuthenticateRequest" />
/// </summary>
public AuthenticateRequestValidator() {
RuleFor(x => x.Username)
.NotNull()
.NotEmpty()
.WithMessage("Username is required");
RuleFor(x => x.Password)
.NotNull()
.NotEmpty()
.WithMessage("Password is required");
}
}
It's being injected into the controller with dot net core's standard DI. Not posting code as it's not relevant to this issue, as its a testing issue.
I'm testing with xunit, Moq and AutoFixture. Here are two tests:
public class SecurityControllerTests {
private readonly IFixture Fixture = new Fixture().Customize(new AutoMoqCustomization {ConfigureMembers = true});
private readonly Mock<IValidator<AuthenticateRequest>> authenticateRequestValidatorMock;
public SecurityControllerTests() {
authenticateRequestValidatorMock = Mock.Get(Fixture.Create<IValidator<AuthenticateRequest>>());
}
[Fact]
public async Task Authenticate_ValidatesRequest() {
// Arrange
var request = Fixture.Create<AuthenticateRequest>();
authenticateRequestValidatorMock
.Setup(x => x.ValidateAsync(It.Is<AuthenticateRequest>(v => v == request), default(CancellationToken)))
.Returns(() => Fixture.Create<Task<ValidationResult>>())
.Verifiable();
var controller = new SecurityController(authenticationServiceMock.Object, tokenisationServiceMock.Object, authenticateRequestValidatorMock.Object);
// Act
await controller.AuthenticateAsync(request);
// Assert
authenticateRequestValidatorMock.Verify();
}
[Fact]
public async Task Authenticate_Returns400_WhenUsernameValidationFails() {
// Arrange
var request = Fixture.Create<AuthenticateRequest>();
var validationResultMock = new Mock<ValidationResult>();
validationResultMock
.SetupGet(x => x.IsValid)
.Returns(() => true);
authenticateRequestValidatorMock
.Setup(x => x.ValidateAsync(It.Is<AuthenticateRequest>(v => v == request), default(CancellationToken)))
.Returns(() => new Task<ValidationResult>(() => validationResultMock.Object));
var controller = new SecurityController(authenticationServiceMock.Object, tokenisationServiceMock.Object, authenticateRequestValidatorMock.Object);
// Act
var result = await controller.AuthenticateAsync(request);
// Assert
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
Assert.IsType<SerializableError>(badRequestResult.Value);
}
}
I need to mock ValidationResult so I can ignore the actual validator logic (which is tested elsewhere) and test the controller logic. There are many other dependencies injected, and much more code, but the pasted code is the crux of the problem and produces the same results when everything else is stripped out.
First test passes, second runs forever when it hits the var validator = await _authenticateRequestValidator.ValidateAsync(req); line in the controller.
Its worth noting that ValidationResult.IsValid is a virtual readonly property.
What is wrong with the second test?
Did you try Asp.Net Core - FluentValidation integration? By this way you dont need to pass Validator depedencies to constructor.
https://github.com/JeremySkinner/FluentValidation/wiki/i.-ASP.NET-Core-integration
FluentValidation fills ModelState in case of validation error and you use it like ;
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
For testing it you set ModelState of you Controllers Mock
var controller = new SecurityController(authenticationServiceMock.Object, tokenisationServiceMock.Object, authenticateRequestValidatorMock.Object);
controller.ModelState.AddModelError("test", "test");
// Act
IActionResult actionResult = await controller.AuthenticateAsync(request);
var badRequestObjectResult = actionResult as BadRequestObjectResult;
Assert.NotNull(badRequestObjectResult);
var serializableError = badRequestObjectResult.Value as SerializableError;
// Assert
Assert.NotNull(result);
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
var serializableError = assert.IsType<SerializableError>(badRequestResult.Value)
Assert.True(((string[])serializableError["test"])[0] == "test");
Leaving ModelState empty would be enough to ignore the actual validator logic i think.
Also FluentValidation have built-in testing api. You can test your validation logic separately.
https://github.com/JeremySkinner/FluentValidation/wiki/g.-Testing
I am writing a Razor Pages app for a University project, which I am required to test. I couldn't find many sources and examples online on testing Razor Pages and I'm trying to follow the examples on this link : https://learn.microsoft.com/en-us/aspnet/core/testing/razor-pages-testing?view=aspnetcore-2.1
My first problem is unit testing:
This is the test method I wrote, it's supposed to check that a value that is filled in the OnGet method on my model is receiving the correct value:
[Fact]
public void OnGet_ViewStores()
{
// Arrange
var httpContext = new DefaultHttpContext();
var modelState = new ModelStateDictionary();
var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
var pageContext = new PageContext(actionContext)
{
ViewData = viewData
};
var storesModel = new StoresModel()
{
PageContext = pageContext,
Url = new UrlHelper(actionContext)
};
#region snippet2
// Act
storesModel.OnGet();
#endregion
#region snippet3
// Assert
var actualStores = wsep1.Services.ViewAllStores(1);
Assert.Equal(storesModel.StoreDetails, actualStores);
#endregion
}
And this is the model which is being checked:
public class StoresModel : PageModel
{
public List<string> StoreDetails { get; set; }
public string Message { get; set; }
public int clientId;
public void OnGet()
{
clientId = (int)HttpContext.Session.GetInt32("clientId");
Message = "Your clientId id is " + clientId;
StoreDetails = wsep1.Services.ViewAllStores(clientId);
}
}
The problem is that the test throws an exception because I am using an HttpContext.Session which is not configures properly in the test. In my real project it is configured beforehand in Startup.cs in this method:
public void ConfigureServices(IServiceCollection services)
{
services.AddWebSocketManager();
services.AddMvc();
services.AddDistributedMemoryCache();
services.AddTransient<ShoppingHandler>();
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(1000);
options.Cookie.HttpOnly = true;
});
}
But I can't seem to find a way to configure this in my test.
My second problem is with integration testing:
I am trying to run a very basic test with the Test Server, this is my test class:
public class IndexPageTest : IClassFixture<TestFixture<Client.Startup>>
{
private readonly HttpClient _client;
public IndexPageTest(TestFixture<Client.Startup> fixture)
{
_client = fixture.Client;
}
#region snippet1
[Fact]
public async Task Request_ReturnsSuccess()
{
// Act
var response = await _client.GetAsync("/");
// Assert
response.EnsureSuccessStatusCode();
}
#endregion
}
I hardly changed the TextFixture class that was included in the demo project in the link I gave at the beginning of the post, all I did was add my services to the configuration method (as I said before, I'm using a Session object and also WebSocketManager in my app).
_client.GetAsync("/") returns a status of "500 - internal server error" and I have no idea why and how to configure these tests to work.
Any ideas would be appreciated, Thanks.
I've see n a lot of discussions surrounding HttpSessionState and asp.net MVC.
I'm trying to write tests for an asp.net application and wondering if it's possible to mock the HttpSessionState and if so, how?
I'm currently using Rhino Mocks and Nunit
Gilbert,
Maybe I'm too late for you. I'm using MSpec, but I think the concepts are similar. I needed to mock several components of the HttpContext in the controllers under test.
I started with these following classes to mock up the necessary (for my purposes) components in the HttpContextBase. I overrode only the necessary pieces inside the classes. Your needs will vary as to the mocks you need in the controller. It's fairly easy to add mocks as needed once you understand the pattern.
public class MockHttpContext : HttpContextBase
{
private readonly HttpRequestBase _request = new MockHttpRequest();
private readonly HttpServerUtilityBase _server = new MockHttpServerUtilityBase();
private HttpSessionStateBase _session = new MockHttpSession();
public override HttpRequestBase Request
{
get { return _request; }
}
public override HttpServerUtilityBase Server
{
get { return _server; }
}
public override HttpSessionStateBase Session
{
get { return _session; }
}
}
public class MockHttpRequest : HttpRequestBase
{
private Uri _url = new Uri("http://www.mockrequest.moc/Controller/Action");
public override Uri Url
{
get { return _url; }
}
}
public class MockHttpServerUtilityBase : HttpServerUtilityBase
{
public override string UrlEncode(string s)
{
//return base.UrlEncode(s);
return s; // Not doing anything (this is just a Mock)
}
}
public class MockHttpSession : HttpSessionStateBase
{
// Started with sample http://stackoverflow.com/questions/524457/how-do-you-mock-the-session-object-collection-using-moq
// from http://stackoverflow.com/users/81730/ronnblack
System.Collections.Generic.Dictionary<string, object> _sessionStorage = new System.Collections.Generic.Dictionary<string,object>();
public override object this[string name]
{
get { return _sessionStorage[name]; }
set { _sessionStorage[name] = value; }
}
public override void Add(string name, object value)
{
_sessionStorage[name] = value;
}
}
Here is how I setup the Controller Context to use the mocks (MSpec). This is setup for the actual tests on the contoller (the tests derive from this class)
public abstract class BlahBlahControllerContext
{
protected static BlahBlahController controller;
Establish context = () =>
{
controller = new BlahBlahController();
controller.ControllerContext = new ControllerContext()
{
Controller = controller,
RequestContext = new RequestContext(new MockHttpContext(), new RouteData()),
};
};
}
To further illustrate here is a test (Specification in MSpec world) that uses the mock session:
[Subject("ACCOUNT: Retrieve Password")]
public class retrieve_password_displays_retrieve_password2_page_on_success : BlahBlahControllerContext
{
static ActionResult result;
static RetrievePasswordModel model;
Establish context = () =>
{
model = new RetrievePasswordModel()
{
UserName = "Mike"
};
};
Because of = () =>
{
result = controller.RetrievePassword(model);
};
It should_return_a_RedirectToRouteResult = () =>
{
result.is_a_redirect_to_route_and().action_name().ShouldEqual("RetrievePassword2");
};
It session_should_contain_UN_value = () =>
{
controller.HttpContext.Session["UN"].ShouldEqual("Mike");
};
It session_should_contain_PQ_value = () =>
{
controller.HttpContext.Session["PQ"].ShouldEqual("Question");
};
}
I realize this doesn't use Rhino Mocks. I hope it illustrates the principles and readers can adopt it to their specific tools and methods.
If you need to instantiate exactly HttpSessionState for legacy code tests, you can leverage FormatterServices mechanism to get uninitialized object. To get it working it is needed to set private _container field though, like in internal constructor
Example:
var state = (HttpSessionState) System.Runtime.Serialization
.FormatterServices.GetUninitializedObject(typeof(HttpSessionState));
var containerFld = typeof(HttpSessionState).GetField(
"_container", BindingFlags.Instance | BindingFlags.NonPublic);
var itemCollection = new SessionStateItemCollection();
itemCollection["element"] = 1;
containerFld.SetValue(
state,
new HttpSessionStateContainer(
"1",
itemCollection,
new HttpStaticObjectsCollection(),
900,
true,
HttpCookieMode.UseCookies,
SessionStateMode.InProc,
false
)
);
look at the HttpSessionStateBase and HttpSessionStateWrapper classes in System.Web.Abstractions. HttpSessionStateBase is the abstract class from which HttpSessionState inherits, and HttpSessionStateWrapper is used to wrap a sealed class in an abstract class, which you can then mock in your tests.
A lot of the System.Web classes are sealed (for example, HttpSessionState), so it's a real pain to test your code when you have methods and classes that interact with them. One pattern I like to use to get around this looks like the following:
public void DoSomething(HttpSessionState state)
{
// take this HttpSeassionState and create an abstract HttpSessionStateBase
// instance
DoSomething(new HttpSessionStateWrapper(state));
}
internal void DoSomething(HttpSessionStateBase state)
{
// my actual logic for working with the session state
}
The public method is difficult to test, because HttpSessionState is sealed, and you can't mock it. However, the internal method operates on an HttpSessionStateBase instance, which you can mock. Note that I've marked it as internal because I don't want the outside world to be able to access that method. However, I do want my tests to be able to access that, so I'll modify my AssemblyInfo.cs to include something like this:
[assembly: InternalsVisibleTo("Vendor.Utilities.Tests")]
Finally, my test for this would look something like this:
[Test]
public void Test_DoSomething()
{
HttpSessionStateBase state = MockRepository.PartialMock<HttpSessionStateBase>();
state.Expect(s => ...);
MyClass.DoSomething(state);
state.VerifyAllExpectations();
}
Hope that helps. Good luck!
This is what I made up based on others contribution...
public class MockWebContext
{
public Mock<RequestContext> RoutingRequestContext { get; private set; }
public Mock<HttpContextBase> Http { get; private set; }
public Mock<HttpServerUtilityBase> Server { get; private set; }
public Mock<HttpResponseBase> Response { get; private set; }
public Mock<HttpRequestBase> Request { get; private set; }
public Mock<HttpSessionStateBase> Session { get; private set; }
public Mock<ActionExecutingContext> ActionExecuting { get; private set; }
public HttpCookieCollection Cookies { get; private set; }
private IDictionary items;
public MockWebContext()
{
RoutingRequestContext = new Mock<RequestContext>(MockBehavior.Loose);
ActionExecuting = new Mock<ActionExecutingContext>(MockBehavior.Loose);
Http = new Mock<HttpContextBase>(MockBehavior.Loose);
Server = new Mock<HttpServerUtilityBase>(MockBehavior.Loose);
Response = new Mock<HttpResponseBase>(MockBehavior.Loose);
Request = new Mock<HttpRequestBase>(MockBehavior.Loose);
Session = new Mock<HttpSessionStateBase>(MockBehavior.Loose);
Cookies = new HttpCookieCollection();
items = new Dictionary<string, object>();
RoutingRequestContext.SetupGet(c => c.HttpContext).Returns(Http.Object);
ActionExecuting.SetupGet(c => c.HttpContext).Returns(Http.Object);
Http.SetupGet(c => c.Request).Returns(Request.Object);
Http.SetupGet(c => c.Response).Returns(Response.Object);
Http.SetupGet(c => c.Server).Returns(Server.Object);
Http.SetupGet(c => c.Session).Returns(Session.Object);
Http.SetupGet(c => c.Items).Returns(items);
Request.Setup(c => c.Cookies).Returns(Cookies);
Request.Setup(c => c.RequestContext).Returns(RoutingRequestContext.Object);
Response.Setup(c => c.Cookies).Returns(Cookies);
Session.Setup(c =>
c.Add(It.IsAny<string>(), It.IsAny<object>())
).Callback((string key, object value)=> items.Add(key, value));
Session.Setup(c =>
c.Remove(It.IsAny<string>())
).Callback((string key) => items.Remove(key));
Session.Setup(c =>
c.Clear()
).Callback(() => items.Clear());
Session.Setup(c =>
c[It.IsAny<string>()]
).Returns((string key)=> items[key]);
}
}
Check out the MvcContrib project.