Question background:
I have two separate projects. One consists of class and a test project, the other a web service. I want to implement mocking against the web service.
Question:
I have a web reference to the web service called 'webService' being consumed by the class 'ProxyHandler', as shown:
private webService _webServiceObject
public ProxyHandler(webService webServiceObject)
{
_webService = webServiceObject;
}
The web service class implements an interface, as shown
public class WebServiceClass:IWebService
Which Implementation?:
Can I get the web reference to be of the type 'IWebService'?
Or, do I need to simply implement a new class based on an interface that consumes the webservice, then this class itself is implemented by the proxy? This would then allow me to mock against the interface, as shown:
Modified Proxy class:
private webService _webServiceHandlerObject;
public ProxyHandler(IwebServiceHandler webServiceHandlerObject)
{
_webServiceHandlerObject = webServiceHandlerObject;
}
Added 'WebServiceHandlerObject:
private webService _webServiceObject;
public Class WebServiceHandler:IwebServiceHandler
public WebServiceHandler(webService webServiceObject)
{
_webServiceObject = webServiceObject;
}
To give:
var proxy = new ProxyHandler(new WebServiceHandler(new webService()));
var mockedProxy = newMock<IwebServiceHandler>();
You abolutely can. Below is an example that I created using a service reference created in Visual Studio. In this example USZipSoap is the web service interface and USZipSoapClient is the implementing class. This all comes out of the box. Often you will need to do some configuration to your service, so below demonstrates how to use a factory method with Unity to resolve your dependencies.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ClassLibrary3.MyService;
using Microsoft.Practices.Unity;
namespace ClassLibrary3
{
public class ProxyHandler
{
public USZipSoap iwebService { get; set; }
public ProxyHandler(USZipSoap iWebService)
{
this.iwebService = iWebService;
}
public string GetZipInfo()
{
return iwebService.GetInfoByZIP("20008").InnerXml;
}
public static IUnityContainer BootstrapContainer()
{
var container = new UnityContainer();
//Simple Registration
//container.RegisterType<USZipSoap, USZipSoapClient>("Simple", new InjectionConstructor(new object[0]));
//Factory registration
container.RegisterType<USZipSoap>(new InjectionFactory(c => ProxyHandler.CreateSoapClient()));
return container;
}
public static USZipSoap CreateSoapClient()
{
var client = new USZipSoapClient();
/*Configure your client */
return client;
}
public static void Main()
{
var container = ProxyHandler.BootstrapContainer();
var proxy2 = container.Resolve<USZipSoap>();
var proxy1 = container.Resolve<ProxyHandler>();
Console.WriteLine(proxy1.GetZipInfo());
Console.ReadLine();
}
}
}
Then your unit tests would look like this with Moq:
[global:Microsoft.VisualStudio.TestTools.UnitTesting.TestClass]
public class MyTestClass
{
[global::Microsoft.VisualStudio.TestTools.UnitTesting.TestMethod]
public void MyTestMethod()
{
//Arrange
var mock = new Mock<USZipSoap>();
var proxy = new ProxyHandler(mock.Object);
//Act
var result = proxy.GetZipInfo();
//Assert
mock.Verify(m => m.GetInfoByZIP("20008"), Times.Once, "Error");
}
}
Related
I have a service that I want to share between other transient services. Right now it's not really a service, but in real life application it will. How would I share my service using dependency injection?
I added some demo code below. The SharedService should be the same object for MyTransientService1 and MyTransientService2 in the "Scope" of MyCreatorService.
The second assert fails, while this is what I'd like to accomplish.
class Program
{
static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
private static IHostBuilder CreateHostBuilder(string[] args)
=> Host.CreateDefaultBuilder(args)
.ConfigureServices((_, services) =>
{
services.AddScoped<SharedService>();
services.AddTransient<MyTransientService1>();
services.AddTransient<MyTransientService2>();
services.AddTransient<MyCreatorService>();
services.AddHostedService<MyHostedService>();
});
}
public class SharedService
{
public Guid Id { get; set; }
}
public class MyTransientService1
{
public SharedService Shared;
public MyTransientService1(SharedService shared)
{
Shared = shared;
}
}
public class MyTransientService2
{
public SharedService Shared;
public MyTransientService2(SharedService shared)
{
Shared = shared;
}
}
public class MyCreatorService
{
public MyTransientService1 Service1;
public MyTransientService2 Service2;
public MyCreatorService(MyTransientService1 s1, MyTransientService2 s2)
{
Service1 = s1;
Service2 = s2;
}
}
public class MyHostedService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public MyHostedService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
var creator1 = _serviceProvider.GetRequiredService<MyCreatorService>();
var creator2 = _serviceProvider.GetRequiredService<MyCreatorService>();
Assert.That(creator1.Service1.Shared.Id, Is.EqualTo(creator1.Service2.Shared.Id));
Assert.That(creator1.Service1.Shared.Id, Is.Not.EqualTo(creator2.Service1.Shared.Id));
return Task.CompletedTask;
}
}
If you use AddScoped, the instance will be the same within the request (for instance for a HTTP Request & Response). If you need your other services to be created everytime they are resolved, you can indeed use AddTransient, but otherwise you can also use AddScoped.
I would also suggest you bind MyHostedService in this manner (if it has anything to do with your described problem), since it seems to be providing a Singleton binding. If a scope of an outer service (one with injected dependencies) is narrower, it will hold hostage injected dependencies. A singleton service with transient dependencies will therefore make all its dependencies singleton, since the outer service will only be created once and its dependencies only resolved once.
UPDATE
After understanding the problem more clearly this should work for you (no other bindings needed):
services.AddTransient<MyCreatorService>(_ =>
{
var shared = new SharedService();
shared.Id = Guid.NewGuid();
return new MyCreatorService(
new MyTransientService1(shared),
new MyTransientService2(shared));
});
Add a constructor to the SharedService class, otherwise the Id is always 000.
Use the following code to create a different scope, so the SharedService will be initialized twice:
MyCreatorService creator1;
MyCreatorService creator2;
using (var scope1 = _serviceProvider.CreateScope())
{
creator1 = scope1.ServiceProvider.GetRequiredService<MyCreatorService>();
}
using (var scope2 = _serviceProvider.CreateScope())
{
creator2 = scope2.ServiceProvider.GetRequiredService<MyCreatorService>();
}
I need to render a view to a string (to send as email). I'm using this implementation.
I want to unit test it, without needing a full ASP.NET Core environment. So I must create an instance of IRazorViewEngine.
The default implementation is RazorViewEngine. I has a mega constructor because each argument needs to be created and each one has a mega constructor, etc., etc. (I can't mock it, I need a live instance.)
Surely there is a simpler way to get an instance?
(Before Core, I could use ViewEngines.Engines. Maybe Core has something similar?)
I tried to do this as well with a unit test similar to this and ran into various issues:
var services = new ServiceCollection();
services.AddMvc();
... ended up needing to add random things into the service collection ...
var serviceProvider = services.BuildServiceProvider();
var razorViewEngine = serviceProvider.GetRequiredService<IRazorViewEngine>();
I ended up going with more of a component test approach using Microsoft.AspNetCore.Mvc.Testing:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
public class ComponentTestStartup : IStartup
{
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
return services.BuildServiceProvider();
}
public void Configure(IApplicationBuilder app)
{
}
}
public class ComponentTestServerFixture : WebApplicationFactory<ComponentTestStartup>
{
public TService GetRequiredService<TService>()
{
if (Server == null)
{
CreateDefaultClient();
}
return Server.Host.Services.GetRequiredService<TService>();
}
protected override IWebHostBuilder CreateWebHostBuilder()
{
var hostBuilder = new WebHostBuilder();
return hostBuilder.UseStartup<ComponentTestStartup>();
}
// uncomment if your test project isn't in a child folder of the .sln file
// protected override void ConfigureWebHost(IWebHostBuilder builder)
// {
// builder.UseSolutionRelativeContentRoot("relative/path/to/test/project");
// }
}
public class RazorViewToStringRendererTests
{
private readonly RazorViewToStringRenderer viewRenderer;
public RazorViewToStringRendererTests()
{
var server = new ComponentTestServerFixture();
var serviceProvider = server.GetRequiredService<IServiceProvider>();
var viewEngine = server.GetRequiredService<IRazorViewEngine>();
var tempDataProvider = server.GetRequiredService<ITempDataProvider>();
viewRenderer = new RazorViewToStringRenderer(viewEngine, tempDataProvider, serviceProvider);
}
[Fact]
public async Task CanRenderViewToString()
{
// arrange
var model = "test model";
// act
var renderedView = await viewRenderer.RenderViewToStringAsync("/Path/To/TestView.cshtml", model);
// assert
Assert.NotNull(renderedView);
Assert.Contains(model, renderedView, StringComparison.OrdinalIgnoreCase);
}
}
TestView.cshtml:
#model string
<h1>#Model</h1>
I've downloaded the nu-get package Hangfire.Dashboard.Authorization
I'm trying configure the OWIN based authorization as per the docs as follows but I get intellisense error DashboardOptions.AuthorizationFilters is obsolete please use Authorization property instead
I also get intellisense error
The type or namespace AuthorizationFilter and ClaimsBasedAuthorizationFilterd not be found
using Hangfire.Dashboard;
using Hangfire.SqlServer;
using Owin;
using System;
namespace MyApp
{
public class Hangfire
{
public static void ConfigureHangfire(IAppBuilder app)
{
GlobalConfiguration.Configuration
.UseSqlServerStorage(
"ApplicationDbContext",
new SqlServerStorageOptions
{ QueuePollInterval = TimeSpan.FromSeconds(1) });
var options = new DashboardOptions
{
AuthorizationFilters = new[]
{
new AuthorizationFilter { Users = "admin, superuser", Roles = "advanced" },
new ClaimsBasedAuthorizationFilter("name", "value")
}
};
app.UseHangfireDashboard("/hangfire", options);
app.UseHangfireServer();
}
}
}
* UPDATE *
Since the above nuget package doesnt work I've attempted to create my own custom filter:
public class HangfireAuthorizationFilter : IAuthorizationFilter
{
public bool Authorize(IDictionary<string, object> owinEnvironment)
{
// In case you need an OWIN context, use the next line,
// `OwinContext` class is the part of the `Microsoft.Owin` package.
var context = new OwinContext(owinEnvironment);
// Allow all authenticated users to see the Dashboard (potentially dangerous).
return context.Authentication.User.Identity.IsAuthenticated;
}
}
How do I restrict to only Admin roles i.e what is the syntax?
You need to make sure the Configure(app) method is called in your Startup.cs class before configuring your hangfire dashboard.
public partial class Startup
{
private static readonly ILog log =
LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod
().DeclaringType);
public void Configuration(IAppBuilder app)
{
//Hangfire Config
GlobalConfiguration.Configuration.UseSqlServerStorage
("HangFireJobs");
app.UseHangfireServer();
log.Debug("Application Started");
ConfigureAuth(app);
//this call placement is important
var options = new DashboardOptions
{
Authorization = new[] { new CustomAuthorizationFilter() }
};
app.UseHangfireDashboard("/hangfire", options);
}
}
Then in your auth config class you can do something as simple as this :
public class CustomAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
if (HttpContext.Current.User.IsInRole("Admin"))
{
return true;
}
return false;
}
}
Defining the dashboard options in this way worked for me -
var options = new DashboardOptions
{
AuthorizationFilters = new List<IAuthorizationFilter>
{
new Hangfire.Dashboard.AuthorizationFilter { Users = "admin, superuser", Roles = "advanced" },
new Hangfire.Dashboard.ClaimsBasedAuthorizationFilter("name", "value")
}
};
I have imported the following namespaces -
using System;
using Owin;
using Hangfire;
using Hangfire.Dashboard;
using System.Collections.Generic;
using Hangfire.SqlServer;
Yes it is showing me the deprecated warning for AuthorizationFilters and suggest to use Authorization, basically the IAuthorizationFilter interface is going to removed in version 2.0, and IDashboardAuthorizationFilter interface has to be used.
For this you can create your own custom filter implementing IDashboardAuthorizationFilter and use this instead.
public class MyAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
//Implement
//Netcore example
return dashboardContext.GetHttpContext().User.Identity.IsAuthenticated;
}
}
I have webapi which for testing purposes I am hosting in owin. I have set it up using autofac. now when I am testing I want to inject moq dependencies. which I am not able to so far. I have read the documentation and did bit of research but I am missing something.
here is the testing code.
[Test]
public void Request_all_airports()
{
const int port = 8086;
AirportCollection moqAirportCollection = new AirportCollection();
moqAirportCollection.Airports = new List<Airport>{new Airport{IATA = "moq",Name = "moqName"}};
using (WebApp.Start<Startup>("http://localhost:" + port))
{
using (var mock = AutoMock.GetLoose())
{
var moqObj = mock.Mock<IAirportService>().Setup(x => x.GetAirports()).Returns(moqAirportCollection);
var client = new HttpClient {BaseAddress = new Uri("http://localhost:" + port)};
var response = client.GetAsync("/api/airport/get").Result;
var body = response.Content.ReadAsStringAsync().Result;
var airportCollection = JsonConvert.DeserializeObject<AirportCollection>(body);
}
}
}
Please have a look. let me know what I am missing. if you want to look at controller code or any other piece do let me know .
here is code for startup
public class Startup
{
public static IContainer container { get; set; }
public void Configuration(IAppBuilder appBuilder)
{
var httpConfig = new HttpConfiguration();
container = AutofacSetup.Register(httpConfig);
WebApiConfig.Register(httpConfig);
appBuilder.UseAutofacMiddleware(container);
appBuilder.UseAutofacWebApi(httpConfig);
appBuilder.UseWebApi(httpConfig);
}
}
Thanks
I think so I have solved it with help from people. here is my code.
var moq = new Mock<IAirportService>();
moq.Setup(x => x.GetAirports()).Returns(moqAirportCollection);
newBuilder.RegisterInstance(moq.Object).As<IAirportService>();
newBuilder.Update(Startup.container);
I havnt rebuild the contrain i just updated it. autofac have behavior to use latest registration so it will use mocked on here.
You are almost there.
In your test you need to register your mock service with your autofac container so that dependencies on IAirportService are resolved with the mock in the application.
One way to achieve this is override the Startup class' Configuration method for each test and put your test DI in there. I've put some comments below to show changes that can be made:
public class Startup
{
public static IContainer container { get; set; }
// make this virtual
public virtual void Configuration(IAppBuilder appBuilder)
{
var httpConfig = new HttpConfiguration();
// have this return the ContainerBuilder instead of the container
var builder = AutofacSetup.Register(httpConfig)
container = builder.Build();
WebApiConfig.Register(httpConfig);
appBuilder.UseAutofacMiddleware(container);
appBuilder.UseAutofacWebApi(httpConfig);
appBuilder.UseWebApi(httpConfig);
}
}
Then in your test class, derive from the Startup class and put your test logic in. Something like this:
public class MyTestCase {
public static Mock<IAirportService> MockObj { get; set; }
private class TestStartup : Startup {
public override void Configuration(IAppBuilder app) {
var httpConfig = new HttpConfiguration();
// this now returns ContainerBuilder instead of the container
var builder = AutofacSetup.Register(httpConfig)
// register your mock, change this to whatever lifetime scope you need
var moqAirportCollection = new AirportCollection();
moqAirportCollection.Airports = new List<Airport>{new Airport{IATA = "moq",Name = "moqName"}};
var mock = AutoMock.GetLoose()
MockObj = mock.Mock<IAirportService>()
.Setup(x => x.GetAirports())
.Returns(moqAirportCollection);
var moqObj = MockObj.Object;
builder.RegisterInstance(moqObj).As<IAirportService>();
container = builder.Build();
WebApiConfig.Register(httpConfig);
appBuilder.UseAutofacMiddleware(container);
appBuilder.UseAutofacWebApi(httpConfig);
appBuilder.UseWebApi(httpConfig);
}
}
[Test]
public void Request_all_airports()
{
using (var server = WebApp.Start<Startup>())
{
var response =
server.CreateRequest("/api/airport/get")
.GetAsync()
.Result;
var body = response.Content.ReadAsStringAsync().Result;
var result = JsonConvert.DeserializeObject<AirportCollection>(body);
// assert something
}
}
}
A unit test should test a single component. In your case, you are trying to test the AirportController through a HTTP query, not the AirportController as a standalone component.
The AirportController class depends on a IAirportService component. In order to test the component without any dependency you created a moq on IAirportService. Now you can instantiate a new AirportController with this moq and run your test using this instance.
If you have a AirportController like this
public class AirportController
{
public AirportController(IAirportService airportService) { /* ... */}
}
The AirportController test should be like this :
[Test]
public void Request_all_airports()
{
AirportCollection moqAirportCollection = new AirportCollection();
var moqAirPort = new Airport{ IATA = "moq",Name = "moqName" };
moqAirportCollection.Airports = new List<Airport>{ moqAirPort };
using (var mock = AutoMock.GetLoose())
{
var moqAirportService = mock.Mock<IAirportService>()
.Setup(x => x.GetAirports())
.Returns(moqAirportCollection);
var testedAirportController = new AirportController(moqAirportService);
AirportCollection airportCollection = testedAirportController.Get();
Assert.AreEquals(1, airportCollection.Length, "Invalid number of airport");
Assert.AreEquals(moqAirPort.Name, airportCollection[0].Name, "Invalid name");
}
}
The following code uses the Castle Windsor 3.0's WCF Integration Facility to register a WCF self-hosted service:
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using Castle.Facilities.WcfIntegration;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
namespace SelfHost
{
[ServiceContract]
public interface IHelloWorldService
{
[OperationContract]
string SayHello(string name);
}
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class HelloWorldService : IHelloWorldService
{
private readonly PerSession _perSession;
public HelloWorldService(PerSession perSession)
{
_perSession = perSession;
}
public string SayHello(string name)
{
return string.Format("Hello, {0} {1}", name, _perSession.Info());
}
}
public class PerSession
{
private readonly string _now;
public PerSession()
{
_now = DateTime.Now.ToString();
}
public string Info()
{
return _now;
}
}
internal class Program
{
private static void Main(string[] args)
{
Uri baseAddress = new Uri("http://localhost:8080/hello");
var container = new WindsorContainer();
container.AddFacility<WcfFacility>();
container.Register(
Component.For<PerSession>().LifeStyle.PerWcfSession(),
Component.For<IHelloWorldService>()
.ImplementedBy<HelloWorldService>()
.AsWcfService(
new DefaultServiceModel()
.AddBaseAddresses(baseAddress)
.AddEndpoints(WcfEndpoint.BoundTo(new BasicHttpBinding()).At("basic"))
.PublishMetadata(o => o.EnableHttpGet())
)
);
Console.WriteLine("The service is ready at {0}", baseAddress);
Console.WriteLine("Press <Enter> to stop the service.");
Console.ReadLine();
}
}
}
Trying to invoke the SayHello method using WcfTestClient.exe results in the following error:
Could not obtain scope for component SelfHost.PerSession. This is most
likely either a bug in custom IScopeAccessor or you're trying to
access scoped component outside of the scope (like a per-web-request
component outside of web request etc)
What is the correct way to use PerWcfSession components?
So I was missing a few things:
The ServiceContract needs to set the SessionMode property
[ServiceContract(SessionMode = SessionMode.Required)]
Likewise the ServiceBehavior needs to set the InstanceContextMode
[ServiceBehavior(IncludeExceptionDetailInFaults = true, InstanceContextMode = InstanceContextMode.PerSession)]
Finally, the service registration needs to change the Lifestyle from the default (Singleton) so that it gets recreated for each request (and the dependencies are re-evaluated) - Transient or PerWcfSession would work.
Also, because we require a session, the binding needs to change from the basicHttpBinding to something that that supports sessions:
Component.For<IHelloWorldService>()
.ImplementedBy<HelloWorldService>()
.LifestyleTransient()
.AsWcfService(
new DefaultServiceModel()
.AddBaseAddresses(baseAddress)
.AddEndpoints(WcfEndpoint.BoundTo(new WSHttpBinding()).At("myBinding"))
.PublishMetadata(o => o.EnableHttpGet())
)
Which makes the final code look like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using Castle.Facilities.WcfIntegration;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
namespace SelfHost
{
[ServiceContract(SessionMode = SessionMode.Required)]
public interface IHelloWorldService
{
[OperationContract]
string SayHello(string name);
}
[ServiceBehavior(IncludeExceptionDetailInFaults = true, InstanceContextMode = InstanceContextMode.PerSession)]
public class HelloWorldService : IHelloWorldService
{
private readonly PerSession _perSession;
public HelloWorldService(PerSession perSession)
{
_perSession = perSession;
}
public string SayHello(string name)
{
return string.Format("Hello, {0} {1}", name, _perSession.Info());
}
}
public class PerSession
{
private readonly string _now;
public PerSession()
{
_now = DateTime.Now.ToString();
}
public string Info()
{
return _now;
}
}
internal class Program
{
private static void Main(string[] args)
{
Uri baseAddress = new Uri("http://localhost:8080/hello");
var container = new WindsorContainer();
container.AddFacility<WcfFacility>();
container.Register(
Component.For<PerSession>().LifeStyle.PerWebRequest,
Component.For<IHelloWorldService>()
.ImplementedBy<HelloWorldService>()
.LifeStyle.PerWebRequest
.AsWcfService(
new DefaultServiceModel()
.AddBaseAddresses(baseAddress)
.AddEndpoints(WcfEndpoint.BoundTo(new WSHttpBinding()).At("myBinding"))
.PublishMetadata(o => o.EnableHttpGet())
)
);
Console.WriteLine("The service is ready at {0}", baseAddress);
Console.WriteLine("Press <Enter> to stop the service.");
Console.ReadLine();
}
}
}