I hava a problem with unit testing asp.net core mvc controller!
the problem is that in my controller, i use sessions:
[HttpPost]
public IActionResult OpretLobby(LobbyViewModel lobby)
{
try
{
//find brugeren der har lavet lobby
var currentUser = HttpContext.Session.GetObjectFromJson<User>("user");
//save as a lobby
ILobby nyLobby = new Lobby(currentUser.Username)
{
Id = lobby.Id
};
SessionExtension.SetObjectAsJson(HttpContext.Session, lobby.Id, nyLobby);
//add to the list
_lobbyList.Add(nyLobby);
return RedirectToAction("Lobby","Lobby",lobby);
}
this all works perfectly well online on the server, nothing wrong here.
BUT when it comes to the demand of unit testing this whole thing, its not so perfect anymore.
basicly the problem is, that i cant get access to my session from a test.. i have tryed in many ways to create mocks and what not, but most of the solutions work for .net framework, and not for .net core for some reason! please help im in pain!
note:
i used a dummy version of a test to isolate this problem:
[Test]
public void TestIsWorking()
{
SessionExtension.SetObjectAsJson(uut.HttpContext.Session, "user", _savedUser);
//ViewResult result = uut.OpretLobby(lobbyViewModel) as ViewResult;
//Assert.AreEqual("OpretLobby", result.ViewName);
Assert.AreEqual(1,1);
}
goes wrong also here trying to set the session for a user :/
It seems that GetObjectFromJson is an extension method. If so, we could not mock static method easily.
I normally create an abstraction for that kind of scenario. Then register it in DI container, and inject the dependency to the controller.
Startup.cs
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IUserSession, UserSession>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
...
}
...
}
Abstraction
public class User
{
public string Username { get; set; }
}
public interface IUserSession
{
User User { get; }
}
public class UserSession : IUserSession
{
private readonly IHttpContextAccessor _httpContextAccessor;
public UserSession(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public User User => _httpContextAccessor.HttpContext.Session.GetObjectFromJson<User>("user");
}
public static class SessionExtensions
{
public static User GetObjectFromJson<User>(
this ISession sesson, string json) where User : new()
{
return new User(); // Dummy extension method just to test OP's code
}
}
Controller
public class HomeController : Controller
{
private readonly IUserSession _userSession;
public HomeController(IUserSession userSession)
{
_userSession = userSession;
}
public IActionResult OpretLobby()
{
var currentUser = _userSession.User;
return View(currentUser);
}
}
}
Unit Tests
using AspNetCore.Controllers;
using Microsoft.AspNetCore.Mvc;
using Moq;
using Xunit;
namespace XUnitTestProject1
{
public class HomeControllerTests
{
private readonly User _user;
public HomeControllerTests()
{
_user = new User {Username = "johndoe"};
}
[Fact]
public void OpretLobby_Test()
{
// Arrange
var mockUserSession = new Mock<IUserSession>();
mockUserSession.Setup(x => x.User).Returns(_user);
var sut = new HomeController(mockUserSession.Object);
// Act
var result = sut.OpretLobby();
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var user = Assert.IsType<User>(viewResult.Model);
Assert.Equal(_user.Username, user.Username);
}
}
}
#nicholas-ladefoged
I would rather recommend you use integration testing when you need to get some content from session
asp.net core has an awesome TestHost nuget package which can help you validate logic using integration way for testing.
Try add a below code snippet
TestServer server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.ConfigureTestServices(services =>
{
services.AddHttpContextAccessor();
}));
var client = server.CreateClient();
var response = await client.GetAsync(""); // I'm using GetAsync just for sample
here you go! You will have a real session that you can test
BTW, Moq library hasn't ability to mock a static methods, so there a lot of issues. I think that will be more easier to use an integration test in your situation
Win solved it! Here is the final test code if anyone is wondering.
// Arrange
var mockUserSession = new Mock<IUserSession>();
mockUserSession.Setup(x => x.User).Returns(_savedUser);
var sut = new LobbyController(FakeSwagCommunication,mockUserSession.Object);
// Act
var result = sut.OpretLobby(_lobbyViewModel);
// Assert
Assert.IsInstanceOf<RedirectToActionResult>(result);
Related
I use ApiFactory for integration tests and mock DB. I took this code from the xUnit tests manual and use it in another project, where it work good.
public class ApiFactory: WebApplicationFactory<Program>
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return builder.ConfigureServices(services =>
{
services.AddDbContext<DatabaseContext>(options =>
options.UseInMemoryDatabase("TestDB"));
})
.UseStartup<Program>();
}
}
[Trait("Category", "Unit")]
public class UserTests : IClassFixture<ApiFactory>
{
private readonly WebApplicationFactory<Program> _factory;
public UserTests(ApiFactory factory)
{
_factory = factory;
}
[Fact]
public void Test1()
{
using (var scope = _factory.Services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
using var db = scope.ServiceProvider.GetService<DatabaseContext>();
db.UserEntity.Add(New UserEntity(){Name = "Test"});
}
var result = _factory.CreateClient().GetAsync("api/v1/user");
var user = await result.ReadResponceContentAsync<UserEntity>();
Assert.Equal("Test", user.Name);
}
}
In this line
using (var scope = _factory.Services.GetRequiredService<IServiceScopeFactory>().CreateScope())
not creating ApiFactory, but filling db.
In this line
var user = await result.ReadResponceContentAsync<UserEntity>();
creating ApiFactory and invoke api, but db in service empty and test is fail.
I have some sample c# code that demonstrates feature management in .net core.
I have this class that exposes an endpoint:
namespace Widgets.Controllers
{
[Authorize]
public class FeatureToggleSampleController : ControllerBase
{
private readonly IFeatureManagerSnapshot featureManager;
public FeatureToggleSampleController(
IFeatureManagerSnapshot featureManager)
{
this.featureManager = featureManager;
}
[Route("toggles/demo")]
[HttpGet]
[FeatureGate(nameof(WidgetToggles.sampleToggleName))]
public string ShowSampleToggle(MeFunctionsRequest request)
{
return "sample toggle is enabled";
}
}
}
we are using a filter, to have the feature mana
[FilterAlias("WidgetsFeatures")]
public class WidgetFeaturesFilter : IWidgetFeaturesFilter
{
private readonly IServiceScopeFactory scopeFactory;
public WidgetFeaturesFilter(IServiceScopeFactory scopeFactory)
{
this.scopeFactory = scopeFactory;
}
public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
{
using var scope = scopeFactory.CreateScope();
var featureRepo = scope.ServiceProvider.GetService<IGenericRepository<WidgetFeature>>();
var feature = featureRepo.Retrieve(filter: "[Name] = #FeatureName", filterParameters: new { context.FeatureName }).FirstOrDefault();
return Task.FromResult(feature != null && feature.Enabled);
}
}
In our startup, we add the service like this:
services.AddFeatureManagement(Configuration.GetSection("FeatureManagement")).AddFeatureFilter<WidgetsFeaturesFilter>();
And, this is the json file where we define the toggles:
{
"FeatureManagement": {
"sampleToggleName": {
"EnabledFor": [
{
"Name": "WidgetsFeatures"
}
]
}
}
}
When the bool is true in the database, hitting http://localhost/toggles/demo returns the "sample toggle is enabled" method message. And when I change the value in the database to false, it 404s in the browser. Everything seems to be working as far as the toggle logic itself.
Now I want to demo how to unit test controllers and other modules that use feature toggles.
So far I have written two unit test code based on a sample i found. The first one works - although I'm not sure if it's because i've written the test correctly. But gives me the string I'm looking for.
The second one fails because the mock is not working. I've asked the mockFeatureManager to return false as far as the toggle status, but it still behaves as if the toggle is enabled.
[Collection("UnitTests")]
public class FeatureToggleSampleControllerShould
{
[Fact]
public async void Return_String_If_Toggle_Enabled()
{
var mockFeatureManager = new Mock<IFeatureManagerSnapshot>();
mockFeatureManager
.Setup(x=>x.IsEnabledAsync(nameof(WidgetsFeatureToggles.sampleToggleName)))
.Returns(Task.FromResult(true));
var featureManager = mockFeatureManager.Object;
Assert.True(await featureManager.IsEnabledAsync(nameof(WidgetsFeatureToggles.sampleToggleName)));
var sampleControler = new FeatureToggleSampleController(TestHelper.Appsettings,featureManager);
Assert.Equal("sample toggle is enabled",sampleControler.ShowSampleToggle());
}
[Fact]
public async void Return_404_If_Toggle_Enabled()
{
var mockFeatureManager = new Mock<IFeatureManagerSnapshot>();
mockFeatureManager
.Setup(x=>x.IsEnabledAsync(nameof(WidgetsFeatureToggles.sampleToggleName)))
.Returns(Task.FromResult(false));
var featureManager = mockFeatureManager.Object;
Assert.False(await featureManager.IsEnabledAsync(nameof(WidgetsFeatureToggles.sampleToggleName)));
var sampleControler = new FeatureToggleSampleController(TestHelper.Appsettings,featureManager);
try{
sampleControler.ShowSampleToggle();
} catch (Exception ex) {
Assert.Contains("404",ex.Message);
}
}
}
I don't think the bug is with the actual code, but the unit test.
But any tips would be appreciated.
EDIT 2
This was the post I was using as an example: IFeatureManager Unit Testing
In my case, I'm not using azure ...
I have the following code:
[Route("resources/avatar")]
[ApiController]
public class AvatarController : ControllerBase
{
private readonly ApplicationDbContext database;
private readonly IWebHostEnvironment environment;
private readonly IUserManagerWrapper userManagerWrapper;
public AvatarController(IUserManagerWrapper userManagerWrapper, IWebHostEnvironment environment,
ApplicationDbContext database)
{
this.userManagerWrapper = userManagerWrapper;
this.environment = environment;
this.database = database;
}
[HttpGet]
[Route("")]
public async Task<IActionResult> Index()
{
if (User == null) return DefaultImage();
var user = await this.userManagerWrapper.GetUserAsync(User);
if ((user?.Avatar?.Length ?? 0) == 0) return DefaultImage();
return File(user.Avatar, "image/jpeg");
}
}
I have an issue with testing this Index Page.
User is a property that comes from ControllerBase and is of type ClaimsPrincipal.
I used a wrapper where I would wrap the usermanager and then use an interface that I would mock.
The problem with this approach is I cannot set this ClaimsPrincipal to null because it is a read-only.
This was my test:
[TestFixture]
public class AvatarController_Should
{
[Test]
public async Task IndexReturnsDefaultImage()
{
var hostingEnvironmentMock = new Mock<IWebHostEnvironment>();
var dabataseName = nameof(IndexReturnsDefaultImage);
var options = AvatarTestUtil.GetOptions(dabataseName);
var userManagerWrapperMock = new Mock<IUserManagerWrapper>();
using (var actAndAssertContext = new ApplicationDbContext(options))
{
var sut = new AvatarController(userManagerWrapperMock.Object, hostingEnvironmentMock.Object, actAndAssertContext);
}
}
}
public class AvatarTestUtil
{
public static DbContextOptions<ApplicationDbContext> GetOptions(string databaseName)
{
var serviceCollection = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
return new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName)
.UseInternalServiceProvider(serviceCollection)
.Options;
}
}
}
I am open to using a completely new approach.
This was how I used to do test on the identity before, but I am stuck now.
Looking at the source code for ControllerBase, we can see User is defined as so
public ClaimsPrincipal User => HttpContext?.User;
So the User actually comes from HttpContext. But HttpContext is readonly as well. Digging into the source code deeper, though, we can see that HttpContext is derived from ControllerContext
public HttpContext HttpContext => ControllerContext.HttpContext;
Alas! ControllerContext actually has a setter in the concrete implementation!
public ControllerContext ControllerContext { get; set; }
We could set up a whole new ControllerContext here if we wanted. But we really just need ControllerContext.User. Luckily, that has a setter too. Since you only really need to set the User, we can do so directly here rather than newing up another ControllerContext.
using (var actAndAssertContext = new ApplicationDbContext(options))
{
var sut = new AvatarController(userManagerWrapperMock.Object, hostingEnvironmentMock.Object, actAndAssertContext);
sut.ControllerContext.HttpContext.User = null;
}
I have dot net core website , based on dependency injection ,the controller is.
public class TokenController : Controller
{
private IConfiguration _config;
private readonly IUserService _iuserService;
public TokenController(IConfiguration config, IUserService iUserService)
{
_config = config;
_iuserService = iUserService;
}
public UserViewModel Authenticate(LoginModel login)
{
UserViewModel user = null;
user = this._iuserService.Login(login.Username, login.Password);
return user;
}
}
and i want to do unit test for Authenticate method ,the unit test class is ,
[TestClass]
public class TokenUnitTest
{
private IUserService _IUserService;
private IConfiguration _config;
[TestInitialize]
public void Setup()
{
var mockIUserService = new Mock<IUserService>();
UserViewModel returnedobj = new UserViewModel();
returnedobj.Email = "sam#sam.com";
returnedobj.Name = "sam";
mockIUserService.Setup(x => x.Login("sam", "123")).Returns(returnedobj);
_IUserService = mockIUserService.Object;
var _configurationRoot = new Mock<IConfigurationRoot>();
_configurationRoot.SetupGet(x => x[It.IsAny<string>()]).Returns("Key");
this._config = _configurationRoot.Object;
}
[TestMethod]
public void Authenticate()
{
////arrange
LoginModel _LoginModel = new LoginModel("sam", "123");
var config = InitConfiguration();
var clientId = config["CLIENT_ID"];
TokenController _TokenController = new TokenController(config, _IUserService);
////act
UserViewModel LoginnedUser = _TokenController.Authenticate(_LoginModel);
////assert
Assert.IsNotNull(LoginnedUser);
}
}
The problem is when i put break point at authenticate method it return the returnd data that exist in setupMock. Why it doesn't go to database and return the true data.
the second problem is the breakpoint doesn't enter in UserService ,it deal with interface ,, why dependency injection doesn't work ??
For an integration test no mocking is required:
[TestInitialize]
public void Setup()
{
_IUserService = new UserService(); // create a real instance
}
I'm a sitecore developer and I want to create a sample sitecore helix unit testing project for testing out our "HomeBottomContentController" controller:
public class HomeBottomContentController : GlassController
{
private readonly ISitecoreContext _iSitecoreContext;
public HomeBottomContentController(ISitecoreContext iSitecoreContext)
{
_iSitecoreContext = iSitecoreContext;
}
public override ActionResult Index()
{
var model = _iSitecoreContext.GetCurrentItem<Home_Control>();
return View("~/Views/HomeBottomContent/HomeBottomContent.cshtml", model);
}
}
I have created a WTW.Feature.HomeBottomContent.Tests project, for the purpose of testing this entire component using helix unit testing. In it I have a UnitTest1.cs file with following:
namespace WTW.Feature.HomeBottomContent.Tests
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void Test_ISitecoreContextInsertion()
{
var iSitecoreContext = Mock.Of<Glass.Mapper.Sc.ISitecoreContext>();
HomeBottomContentController controllerUnderTest = new HomeBottomContentController(iSitecoreContext);
var result = controllerUnderTest.Index() as ViewResult;
Assert.IsNotNull(result);
}
}
}
This test does pass, meaning "result" is NOT null; however, the problem is when I step into the Index() code, I see that the "model" variable is NULL when we do
var model = _iSitecoreContext.GetCurrentItem<Home_Control>();
My question is, how exactly do I change this code to make sure that the "model" in that line does not become null? How do I "mock" an item in unit test code for the _iSitecoreContext so that it has a "Home_Control" template with legit values for its fields? Would that even be the right approach? Most online sources I've found do not have a similar scenario, I'm looking for the shortest code possible.
Another question I had is, how can I test the below Index() method in my [TestMethod], given that the SitecoreContext is declared inside the Index() method, rather than received in the HomeBottomContentController constructor like above? Is there a way to do that from the [TestMethod], or we have to send in the SitecoreContext into the HomeBottomContentController constructor or into the Index() method as a parameter?
public override ActionResult Index()
{
var context = new SitecoreContext();
var model = context.GetCurrentItem<Home_Control>();
return View("~/Views/HomeBottomContent/HomeBottomContent.cshtml", model);
}
In that case you would need to mock the desired behavior on the mocked dependency
[TestClass]
public class UnitTest1 {
[TestMethod]
public void Test_ISitecoreContextInsertion() {
//Arrange
var model = new Home_Control() {
//...populate as needed
}
var iSitecoreContext = new Mock<Glass.Mapper.Sc.ISitecoreContext>();
//Setup the method to return a model when called.
iSitecoreContext.Setup(_ => _.GetCurrentItem<Home_Control>()).Returns(model);
var controllerUnderTest = new HomeBottomContentController(iSitecoreContext.Object);
//Act
var result = controllerUnderTest.Index() as ViewResult;
//Assert
Assert.IsNotNull(result);
Assert.IsNotNull(result.Model);
//...other assertions.
}
}
UPDATE
Creating the context within the action tightly couples it to the context, making it almost impossible to mock. That is the reason explicit dependencies are injected
You can do something like that:
public class HomeBottomContentController : GlassController
{
private readonly ISitecoreContext _iSitecoreContext;
public HomeBottomContentController(ISitecoreContext iSitecoreContext)
{
_iSitecoreContext = iSitecoreContext;
}
public override ActionResult Index()
{
var model = this.GetCurrentItem();
return View("~/Views/HomeBottomContent/HomeBottomContent.cshtml", model);
}
protected virtual Home_Control GetCurrentItem()
{
return _iSitecoreContext.GetCurrentItem<Home_Control>();
}
}
namespace WTW.Feature.HomeBottomContent.Tests
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void Test_ISitecoreContextInsertion()
{
var iSitecoreContext = Mock.Of<Glass.Mapper.Sc.ISitecoreContext>();
var controllerUnderTest = new FakeHomeBottomContentController(iSitecoreContext);
var result = controllerUnderTest.Index() as ViewResult;
Assert.IsNotNull(result);
}
}
public class FakeHomeBottomContentController : HomeBottomContentController
{
public FakeHomeBottomContentController(ISitecoreContext iSitecoreContext) : base(iSitecoreContext)
{
}
protected override Home_Control GetCurrentItem()
{
// return instance of Home_Control type
// e.g.
return new Home_Control();
}
}
}