When configuring the builder in "ASP.NET Core", I want to put the data model I created myself.
It is intended to be delivered to middleware or "Add Scoped" services.
I've tried the following code:
TestUtilsSettingModel temp = new TestUtilsSettingModel()
{
Test01 = 1000,
Test03 = "Input!!"
};
//error
builder.Services
.Configure<TestUtilsSettingModel>(temp);
//error
builder.Services
.Configure<TestUtilsSettingModel>(
new Action<TestUtilsSettingModel>(temp));
//https://stackoverflow.com/a/45324839/6725889
builder.Configuration["TestUtilsSetting:Test01"] = 1000.ToString();
Test Code and project here.
"Configuration["TestUtilsSetting:Test01"]" works but
You have to put all the data yourself.
Can't you just pass the whole data model?
Here is the test code:
class to receive options:
Code here.
public interface ITestUtils
{
}
public class TestUtils: ITestUtils
{
private readonly TestUtilsSettingModel _appSettings;
public TestUtils(IOptions<TestUtilsSettingModel> appSettings)
{
_appSettings = appSettings.Value;
}
}
Inject from "Program.cs" or "Startup.cs"
Code here.
builder.Services.AddScoped<ITestUtils, TestUtils>();
Used in controller constructor :
Code here.
public WeatherForecastController(
ILogger<WeatherForecastController> logger
, ITestUtils testMiddleware)
{
_logger = logger;
this._TestMiddleware = testMiddleware;
}
I studied 'iservicecollection extension' to solve this problem.
I found a solution.
The following code is equivalent to
'builder.Configuration["TestUtilsSetting:Test01"] = 1000.ToString();'
builder.Services.Configure<TestUtilsSettingModel>(options =>
{
options.Test01 = 1000;
});
You still have to put each one in.
('options = temp' not work)
So do something like this:
Add the following method to 'TestUtilsSettingModel'.
public void ToCopy(TestUtilsSettingModel testUtilsSettingModel)
{
this.Test01 = testUtilsSettingModel.Test01;
this.Test02 = testUtilsSettingModel.Test02;
this.Test03 = testUtilsSettingModel.Test03;
}
In 'Program.cs', pass the model as follows.
builder.Services.Configure<TestUtilsSettingModel>(options =>
{
options.ToCopy(temp);
});
enter image description here
Related
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'm creating a Rest API in .net core 3 (my first one). In that API I did a dll that I call from some API methods.
I want to write some tests on that dll but I have some issues with some dependency injection and getting values set in API ConfigureServices. My main problem is to get an HttpClient by name with a IHttpClientFactory.
My architecture is :
Project WebApi
Project dllApi
Project Tests
Here is my ConfigureServices :
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddHttpClient("csms", c =>
{
c.BaseAddress = Configuration.GetValue<Uri>("ExternalAPI:CSMS:Url");
});
services.AddSingleton(typeof(IdllClass), typeof(dllClass));
}
My class in dll
public class dllClass
{
private readonly IHttpClientFactory ClientFactory;
public dllClass(IHttpClientFactory clientFactory)
{
ClientFactory = clientFactory;
}
public async Task<Credentials> GetCredentials()
{
var request = new HttpRequestMessage(HttpMethod.Get, $"Security/GetCredentials");
using (var client = ClientFactory.CreateClient("csms"))
{
var response = await client.SendAsync(request);
}
return new Credentials();
}
}
I tried different method (moq, Substitute, ...) and the closest I got from my goal was this one below but it doesn't find the HttpClient by name :
public void GetCredentials()
{
var httpClientFactoryMock = Substitute.For<IHttpClientFactory>();
var service = new dllClass(httpClientFactoryMock);
var result = service.GetCredentials().Result;
}
How should I write that test ?
Thank you for your help
As the Comment states. You haven't mocked the CreateClient method. It should look something like the following:
public void GetCredentials()
{
var httpClientFactoryMock = Substitute.For<IHttpClientFactory>();
var csmsTestClient = new HttpClient();
httpClientFactoryMock.CreateClient("csms").Returns(csmsTestClient)
var service = new dllClass(httpClientFactoryMock);
var result = service.GetCredentials().Result;
}
Then you need to setup your HttpClient to point at whatever url you want to test.
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);
I'm using Moq to provide a mocking context for my Oracle db. But when I call _context.Entry with the mocked context, I get an InvalidOperationException.
"No connection string named 'Entities' could be found in the application config file."
I'm already providing a mocked context, so not sure why it's still trying to read connection string to create the context.
// generated code for oracle db
public partial class Entities : DbContext
{
public Entities()
: base("name=Entities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<ACTIVITY_CODE> ACTIVITY_CODE { get; set; }
}
// my code
public partial class Entities : System.Data.Entity.DbContext
{
public Entities(string scon) : base(scon) { }
}
// my code
public partial class ActivityCodeService
{
private Entities _context;
public ActivityCodeService(Entities context)
{
this._context = context;
}
public ACTIVITY_CODE Update(ACTIVITY_CODE item)
{
ACTIVITY_CODE ret = null;
var found = Read(item.ACT_ID);
if (found != null)
{
_context.Entry<ACTIVITY_CODE>(found).CurrentValues.SetValues(item); // throws InvalidOperationException "No connection string named 'Entities' could be found in the application config file."
_context.SaveChanges();
ret = item;
}
return ret;
}
}
// test code
[TestMethod]
public void activity_code_update_test()
{
// arrange
var mockSet = new Mock<DbSet<ACTIVITY_CODE>>();
mockSet.As<IQueryable<ACTIVITY_CODE>>().Setup(o => o.Provider).Returns(testData.Provider);
mockSet.As<IQueryable<ACTIVITY_CODE>>().Setup(o => o.Expression).Returns(testData.Expression);
mockSet.As<IQueryable<ACTIVITY_CODE>>().Setup(o => o.ElementType).Returns(testData.ElementType);
mockSet.As<IQueryable<ACTIVITY_CODE>>().Setup(o => o.GetEnumerator()).Returns(testData.GetEnumerator());
var mockContext = new Mock<Entities>();
mockContext.Setup(c => c.ACTIVITY_CODE).Returns(mockSet.Object);
var expected = new ACTIVITY_CODE() { ACT_ID = 1, ACT_CODE = "code 2", ACT_DESC = "desc 2" };
var target = new ActivityCodeService(mockContext.Object);
// act
target.Update(expected);
}
But if I don't use _context.Entry, then the test runs fine which is expected. So does that mean _context.Entry is creating another internal context and ignoring my mocked context?
// my code
public ACTIVITY_CODE Update(ACTIVITY_CODE item)
{
var ret = _context.ACTIVITY_CODE.FirstOrDefault(o => o.ACT_ID == item.ACT_ID);
if (ret != null)
{
ret.ACT_CODE = item.ACT_CODE;
ret.ACT_DESC = item.ACT_DESC;
_context.SaveChanges(); // this will work fine with Moq's mocked context
}
return ret;
}
Entry isn't, and can't be, mocked by Moq as it's not virtual so it is still going to try to use the database that it believes is there. That's why it's looking for a connection string.
What I have been able to do which has worked well is to abstract that function call into a virtual method that I had enough control over to actually mock.
Alternatives:
There are some tools based on answers to other questions that have the same base problem. Looks like TypeMock and JustMock may be able to work around the issue.
Additionally, it looks like MS Fakes should be able to shim it. After a little investigation it looks like it'd work something like this:
ShimDbEntityEntry<TestModel> entryMock = new ShimDbEntityEntry<TestModel>();
ShimDbPropertyValues mockValues = new ShimDbPropertyValues();
mockValues.SetValuesObject = (newValues) => { }; // Manually do something here
entryMock.CurrentValuesGet = () => mockValues;
ShimDbContext.AllInstances.EntryOf1M0<TestModel>((ctx, target) => entryMock);
I am having a hard time wrapping my head around a unit testing pattern when trying to test an ASP.Net MVC controller/action.
With the following code, I am trying to write a test for the ShowPerson() method:
public class PersonController : Controller
{
private IDataAccessBlock _dab;
public PersonController()
: this(new DataAccessBlock())
{ }
public PersonController(IDataAccessBlock dab)
{
_dab = dab;
}
public ActionResult ShowPerson(PersonRequestViewModel personRequest)
{
var person = GetPersonViewModel(personRequest);
return View("Person", person);
}
private PersonViewModel GetPersonViewModel(PersonRequestViewModel personRequest)
{
var personService = new CommonDomainService.PersonService(_dab);
var dt = personService.GetPersonInfo(personRequest.Id);
var person = new PersonViewModel();
if (dt.Rows.Count == 1)
{
person.FirstName = dt.Rows[0]r["FIRSTNAME"]);
person.LastName = dt.Rows[0]["LASTNAME"];
}
return person;
}
}
The test that I am using (using nUnit and Moq):
[Test]
public void ShowPerson_Action_Should_Return_Person_View()
{
// Arrange
string expected = "Person";
Mock<PersonRequestViewModel> personRequestViewModelMock = new Mock<PersonRequestViewModel>();
personRequestViewModelMock.SetupProperty(f => f.Id, 123456);
Mock<IDataAccessBlock> mockDab = new Mock<IDataAccessBlock>();
PersonController personController = new PersonController(mockDab.Object);
// Act
ViewResult result = personController.ShowPerson(personRequestViewModelMock.Object) as ViewResult;
// Assert
personRequestViewModelMock.Verify();
result.Should().Not.Be.Null();
if (result != null) Assert.AreEqual(expected, result.ViewName, "Unexpected view name");
}
Everything seems to go fine, until the line if (dt.Rows.Count == 1) is encountered. I get an "Object reference not set to an instance of an object."
I assume that there must be something funky with the way that the following two lines are written:
var personService = new CommonDomainService.PersonService(_dab);
var dt = personService.GetPersonInfo(personRequest.Id);
but I'm not sure where to go from here. I have a lot of code that would look like this. Am I doing something wrong, or is there an actual way to test this?
Thanks for any help or pointers.
Is your CommonDomainService.PersonService is some kind of webservice which is hosted in your webapplication, when you are running your tests your webapplication will not be running and service will not be accessible.
Ideally, your controller has a dependency on CommonDomainService.PersonService which you are creating in your private method,instead this should be injected into the Controller(like you do DataAccess block), and mock it up in your test method.
write private readonly IDataAccessBlock _dab; instead of private IDataAccessBlock _dab;