We are using ServiceStack for our .NET backend and I am trying to work on getting unit testing into the project. However there are some automated tools within ServiceStack that makes it a bit complicated to isolate the units so I could really use some advice. In the example below I would like to unit test a simple service that basically does the following:
Takes a request DTO
Passes the DTO to the repository
Gets back a domain model
If the model exists, it maps it to a responseDTO using Automapper and returns it as a part of an IHTTPResult
So the problem I have is that it seems like Automapper is automatically added to the ServiceStack application and in the application the mapper are registered by just calling:
AutoMapping.RegisterConverter().
So how could I inject this into the service to be able to do the unittest?
Example test:
using AutoMapper;
using FluentAssertions;
using NSubstitute;
namespace Api.Services.Tests.Unit;
public class OrderApiServiceTests
{
private readonly OrderApiService _sut;
private readonly IOrderApiRepository accountApiRepository = Substitute.For<IOrderApiRepository>();
public OrderApiServiceTests()
{
_sut = new OrderApiRepository(orderApiRepository);
var config = new MapperConfiguration(cfg => ApiDtoMapping.Register());
var mapper = config.CreateMapper();
}
[Fact]
public async Task Get_ShouldReturnAccount_WhenAccountExistsAsync()
{
// Arrange
var order = new Order
{
Name = "MyOrder",
Value = 1000,
};
var expectedResponse = new OrderApiDto
{
Name = "MyOrder",
Value = 1000,
};
orderApiRepository.GetAsync(Arg.Any<GetOrder>()).Returns(order);
// Act
var result = await _sut.Get(new GetOrder());
// Assert
result.StatusCode.Should().Be(System.Net.HttpStatusCode.OK);
result.Response.Should().BeEquivalentTo(expectedResponse);
}
}
Added a full example including all files:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
app.UseHttpsRedirection();
}
app.UseServiceStack(new AppHost());
app.Run();
// Configure.AppHost.cs
using Funq;
using ssUnitTests.ServiceInterface;
[assembly: HostingStartup(typeof(ssUnitTests.AppHost))]
namespace ssUnitTests;
public class AppHost : AppHostBase, IHostingStartup
{
public void Configure(IWebHostBuilder builder) => builder
.ConfigureServices(services =>
{
});
public AppHost() : base("ssUnitTests", typeof(MyServices).Assembly) { }
public override void Configure(Container container)
{
container.RegisterAutoWiredAs<OrderRepository, IOrderRepository>().ReusedWithin(ReuseScope.None);
// Configure ServiceStack only IOC, Config & Plugins
SetConfig(new HostConfig
{
UseSameSiteCookies = true,
});
Mappings.RegisterConverters();
}
}
// Mappings.cs
using ssUnitTests.ServiceModel;
namespace ssUnitTests;
public static class Mappings
{
public static void RegisterConverters()
{
AutoMapping.RegisterConverter((Order from) =>
{
var to = from.ConvertTo<OrderDto>();
to.DtoProperty = from.BaseProperty + "Dto";
return to;
});
}
}
// IOrderRepository.cs
using ssUnitTests.ServiceModel;
namespace ssUnitTests.ServiceInterface;
public interface IOrderRepository
{
Order GetOrder();
}
// Order.cs
namespace ssUnitTests.ServiceModel;
public class Order
{
public string Name { get; set; }
public string BaseProperty { get; set; }
}
// OrderDto.cs
namespace ssUnitTests.ServiceModel;
public class OrderDto
{
public string Name { get; set; }
public string DtoProperty { get; set; }
}
// OrderRequest.cs
using ServiceStack;
namespace ssUnitTests.ServiceModel;
[Route("/order")]
public class OrderRequest : IReturn<OrderDto>
{
public int Id { get; set; }
}
// UnitTest.cs
using NSubstitute;
using NUnit.Framework;
using ssUnitTests.ServiceInterface;
using ssUnitTests.ServiceModel;
namespace ssUnitTests.Tests;
public class UnitTest
{
private readonly MyServices _sut;
private readonly IOrderRepository _repository = Substitute.For<IOrderRepository>();
public UnitTest()
{
_sut = new MyServices(_repository);
}
[Test]
public void Get_ShouldReturn_OrderDto()
{
var order = new Order
{
Name = "MyName",
BaseProperty = "MyBaseProperty"
};
_repository.GetOrder().Returns(order);
var response = (OrderDto)_sut.Any(new OrderRequest { Id = 1 });
Assert.That(response.Name.Equals(order.Name));
Assert.That(response.DtoProperty.Equals(order.BaseProperty + "Dto"));
}
}
ServiceStack.dll does not have any dependencies to any 3rd Party Libraries, e.g. it's built-in AutoMapping is a completely different stand-alone implementation to AutoMapper.
If you're using AutoMapper you can ignore ServiceStack's AutoMapping which is completely unrelated.
Related
Trying to build integration test with connection to db in ServiceStack.
My ServiceStack app is working fine, but when I run simple test I got this error message in line:22
System.MissingMethodException: 'Method not found: 'Int32 ServiceStack.DataAnnotations.CustomFieldAttribute.get_Order()'.'
There is a lite cod:
using ServiceStack;
using ServiceStack.OrmLite;
using ServiceStack.Data;
using NUnit.Framework;
using ServiceStack.DataAnnotations;
using System.Collections.Generic;
namespace oth.Tests.IntegrationTests
{
public class AppHost2 : AppSelfHostBase
{
public AppHost2() : base("Customer REST Example", typeof(CustomerService).Assembly) { }
public override void Configure(Container container)
{
var connectionString = "Host=localhost;Port=5432;Database=test_1234;Username=postgres;Password=local";
container.Register<IDbConnectionFactory>(c =>
new OrmLiteConnectionFactory(connectionString, PostgreSqlDialect.Provider));
using var db = container.Resolve<IDbConnectionFactory>().Open();
db.CreateTableIfNotExists<Customer>();
}
}
public class Customer
{
[AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
}
[Route("/customers", "GET")]
public class GetCustomers : IReturn<GetCustomersResponse> { }
public class GetCustomersResponse
{
public List<Customer> Results { get; set; }
}
public class CustomerService : Service
{
public object Get(GetCustomers request)
{
return new GetCustomersResponse { Results = Db.Select<Customer>() };
}
}
public class CustomerRestExample
{
const string BaseUri = "http://localhost:2000/";
ServiceStackHost appHost;
public CustomerRestExample()
{
//Start your AppHost on TestFixture SetUp
appHost = new AppHost2()
.Init()
.Start(BaseUri);
}
[OneTimeTearDown]
public void OneTimeTearDown() => appHost.Dispose();
/* Write your Integration Tests against the self-host instance */
[Test]
public void Run_Customer_REST_Example()
{
var client = new JsonServiceClient(BaseUri);
var all = client.Get(new GetCustomers());
Assert.That(all.Results.Count, Is.EqualTo(0));
}
}
}
Anytime you see a missing type or missing method exceptions when using the MyGet pre-release packages it means you have a dirty installation (i.e. using pre-release packages from different build times).
In which case you'd need to Clear your Nuget packages cache and download the latest packages again, which ensures all your packages are from the latest same build:
$ dotnet nuget locals all -clear
Since the new .net 3.2 preview no longer has a startup.cs with its ConfigureServices function, I am at a loss to figure out how to implement .AddCors. The old way of adding services essentially was to add, then use a service. It doesn't look like that's the way to do it anymore. What is the proper code to add CORS?
Program.cs
using System.Threading.Tasks;
using Microsoft.AspNetCore.Blazor.Hosting;
using Microsoft.Extensions.DependencyInjection;
using BlazorDemo.Shared;
namespace BlazorDemo
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Services.AddSingleton<IDataLayer, DataLayer>();
builder.Services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder
.AllowAnyOrigin());
});
builder.RootComponents.Add<App>("app");
await builder.Build().RunAsync();
}
}
}
Data.cs
namespace BlazorDemo.Shared
{
public class Data
{
public Country[] data { get; set; }
}
}
DataLayer.cs
using System;
using System.Threading.Tasks;
using System.Net.Http;
using Microsoft.AspNetCore.Components;
namespace BlazorDemo.Shared
{
public interface IDataLayer
{
Task<Country[]> FetchCountries(string sortField, bool sortDesc);
Task<Country[]> FetchCountries();
}
public class DataLayer : IDataLayer
{
public DataLayer(HttpClient httpClient)
{
this.httpClient = httpClient;
}
HttpClient httpClient;
public async Task<Country[]> FetchCountries(string sortField, bool sortDesc)
{
var url = $"http://outlier.oliversturm.com:8080/countries?sort[0][selector]={sortField}&sort[0][desc]={sortDesc}&take=10";
var data = await httpClient.GetJsonAsync<Data>(url);
return data.data;
}
public async Task<Country[]> FetchCountries()
{
Country[] Countries;
try
{
var url = $"http://outlier.oliversturm.com:8080/countries";
var data = await httpClient.GetJsonAsync<Data>(url);
Countries = ((Data)data).data;
}
catch (Exception e)
{
Country c = new Country() { name = "DD", areaKM2 = 2, population = 50, _id = e.Message };
Countries = new Country[] { c };
}
return Countries;
}
}
}
I'm currently porting an application from ASP.NET 4 to ASP.NET Core. I want to use attribute based routing while having the ability to localize the URLs.
The legacy application was using an approach using a custom IDirectRouteProvider. Since I didn't find the corresponding type in ASP.NET Core, I went with a solution inspired by https://www.strathweb.com/2015/11/localized-routes-with-asp-net-5-and-mvc-6/. Here's the implementation using an IApplicationModelConvention
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(o =>
{
o.Conventions.Insert(0, new LocalizedRouteConvention());
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseRequestLocalization(new RequestLocalizationOptions { ... });
app.UseMvc();
}
}
public class LocalizedRouteConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
foreach (var action in controller.Actions)
{
var attributes = action.Attributes.OfType<RouteAttribute>().ToArray();
if (!attributes.Any()) return;
foreach (var attribute in attributes)
{
SelectorModel defaultSelector = action.Selectors.First();
foreach (var localizedVersion in GetLocalized(attribute.Template))
{
if (!action.Selectors.Any(s => s.AttributeRouteModel.Template == localizedVersion.Template))
{
action.Selectors.Insert(0, new SelectorModel(defaultSelector)
{
AttributeRouteModel = localizedVersion,
ActionConstraints =
{
new CultureActionConstraint { Culture = ((LocalizedRouteAttribute) localizedVersion.Attribute).Culture }
}
});
}
}
}
}
}
}
}
public class LocalizedRouteAttribute : RouteAttribute
{
public LocalizedRouteAttribute(string template) : base(template)
{
}
public string Culture { get; set; }
}
public class CultureActionConstraint : IActionConstraint
{
public string Culture { get; set; }
public int Order => 0;
public bool Accept(ActionConstraintContext context)
{
return CultureInfo.CurrentCulture.TwoLetterISOLanguageName == Culture;
}
}
Now, this approach works and the localized routes are only available when the correct request culture is set. However, when I use Html.ActionLink(...) or any other function that uses IUrlHelper.GetVirtualPathData(), the default route is returned instead of the localized one.
As far as I understand, the IUrlHelper will check the IRouteConstraints of a Route but it doesn't seem to respect the IActionConstraint. Unfortunately, I haven't found a way to set custom IRouteConstraints in my IApplicationModelConvention.
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 wiring up Autofac dependency injection within my ASP.NET MVC 5 web application using OWIN middleware (so using startup.cs instead of global.asax), and trying to use property injection to set a public variable within a Controller.
I'm playing around with property injection to have Autofac automatically set the Test property in the LoginController.
public interface ITest
{
string TestMethod();
}
public class Test : ITest
{
public string TestMethod()
{
return "Hello world!";
}
}
public class LoginController : Controller
{
public ITest Test { get; set; }
public LoginController()
{
var aaa = Test.TestMethod();
// Do other stuff...
}
}
Here's what my startup.cs looks like. I have been playing around, so some of this code might not be needed (or causing my issue?).
public class Startup
{
public void Configuration(IAppBuilder app)
{
var builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired();
builder.RegisterType<Test>().As<ITest>().SingleInstance();
builder.Register(c => new Test()).As<ITest>().InstancePerDependency();
builder.RegisterType<ITest>().PropertiesAutowired();
builder.RegisterType<LoginController>().PropertiesAutowired();
builder.RegisterModelBinderProvider();
builder.RegisterFilterProvider();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
app.UseAutofacMiddleware(container);
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
// Some other stuff...
}
}
So, the 'Test' public property is always null, and therefore breaks on runtime.
Any ideas what could be my issue? Thanks advance for your help! :)
So, the 'Test' public property is always null, and therefore breaks on runtime.
It's not always null. It's null in the constructor because Autofac (actually ALL code) cannot set properties until the constructor is finished.
public class LoginController : Controller
{
public ITest Test { get; set; }
public LoginController()
{
// Test is null, will always be null here
var aaa = Test.TestMethod();
}
}
A super dummied down version of autofac does something like:
var controller = new LoginController();
controller.Test = new Test();
If you need to execute code after the property is set you could do something hacky like the following (but really you should just be using constructor injection):
public class LoginController : Controller
{
private ITest _test;
public ITest Test
{
get { return _test; }
set
{
var initialize = (_test == null);
_test = value;
if (initialize)
{
Initialize();
}
}
}
public LoginController()
{
}
private void Initialize()
{
var aaa = Test.TestMethod();
}
}
Again the more logical way would be to just do:
public class LoginController : Controller
{
private readonly ITest _test;
public LoginController(ITest test)
{
_test = test;
var aaa = _test.TestMethod();
// Do other stuff...
}
}