I was asked to create this Http azure function bellow. I'm trying to write a mock unit test to call this processEmail. I'm guessing my req will be the entry point. My unit test should have different email value to test. If I could have an example from my function bellow that would be great.
public async Task<IActionResult> ProcessEmail(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]
HttpRequest req, ILogger log) {
log.SmpLogInformation("Initializing SendGrid ProcessEmail");
var client =
new SendGridClient("key");
var requestBody = new StreamReader(req.Body).ReadToEnd();
var data = JsonConvert.DeserializeObject<EmailContent>(requestBody);
if(data == null) {
throw new ArgumentNullException("Can't proced further");
}
var message = new SendGridMessage();
message.AddTo(data.Email);
message.SetFrom(new EmailAddress("ff.com"));
message.AddContent("text/html", HttpUtility.HtmlDecode(data.Body));
message.SetSubject(data.Subject);
log.SmpLogDebug("Email sent through Send Grid");
await client.SendEmailAsync(message);
return (ActionResult)new OkObjectResult("Submited sucessfully");
}
public class EmailContent {
public string? Email { get; set; }
public string? Subject { get; set; }
public string Body { get; set; } = "";
}
}
Firstly, you need to mock your SendGridClient otherwise you will be making actual requests during your unit tests which wouldn't be great.
Looking at the SendGrid code, SendGridClient implements an interface ISendGridClient. Instead of new'ing up the client using var client = new SendGridClient("key");, you could use dependency injection to inject an instance of ISendGridClient via the constructor:
public class ProcessEmail
{
private readonly ISendGridClient _client;
public ProcessEmail(ISendGridClient client)
{
_client = client;
}
You can then remove this line:
var client = new SendGridClient("key");
And then a slight change to this line to use the injected object:
await _client.SendEmailAsync(message);
Then when you come to write your unit test, you will be able to have a mock for the ISendGridClient interface which allows you to setup and verify behaviour of objects. Here's an example using Moq:
[TestClass]
public class ProcessEmailTests
{
private readonly Mock<ISendGridClient> _mockSendGridClient = new Mock<ISendGridClient>();
private readonly Mock<ILogger> _mockLogger = new Mock<ILogger>();
private ProcessEmail _processEmail;
private MemoryStream _memoryStream;
[TestInitialize]
public void Initialize()
{
// initialize the ProcessEmail class with a mock object
_processEmail = new ProcessEmail(_mockSendGridClient.Object);
}
[TestMethod]
public async Task GivenEmailContent_WhenProcessEmailRuns_ThenEmailSentViaSendgrid()
{
// arrange - set up the http request which triggers the run method
var expectedEmailContent = new ProcessEmail.EmailContent
{
Subject = "My unit test",
Body = "Woohoo it works",
Email = "unit#test.com"
};
var httpRequest = CreateMockRequest(expectedEmailContent);
// act - call the Run method of the ProcessEmail class
await _processEmail.Run(httpRequest, _mockLogger.Object);
// assert - verify that the message being sent into the client method has the expected values
_mockSendGridClient
.Verify(sg => sg.SendEmailAsync(It.Is<SendGridMessage>(sgm => sgm.Personalizations[0].Tos[0].Email == expectedEmailContent.Email), It.IsAny<CancellationToken>()), Times.Once);
}
private HttpRequest CreateMockRequest(object body = null, Dictionary<string, StringValues> headers = null, Dictionary<string, StringValues> queryStringParams = null, string contentType = null)
{
var mockRequest = new Mock<HttpRequest>();
if (body != null)
{
var json = JsonConvert.SerializeObject(body);
var byteArray = Encoding.ASCII.GetBytes(json);
_memoryStream = new MemoryStream(byteArray);
_memoryStream.Flush();
_memoryStream.Position = 0;
mockRequest.Setup(x => x.Body).Returns(_memoryStream);
}
if (headers != null)
{
mockRequest.Setup(i => i.Headers).Returns(new HeaderDictionary(headers));
}
if (queryStringParams != null)
{
mockRequest.Setup(i => i.Query).Returns(new QueryCollection(queryStringParams));
}
if (contentType != null)
{
mockRequest.Setup(i => i.ContentType).Returns(contentType);
}
mockRequest.Setup(i => i.HttpContext).Returns(new DefaultHttpContext());
return mockRequest.Object;
}
}
Full function code:
public class ProcessEmail
{
private readonly ISendGridClient _client;
public ProcessEmail(ISendGridClient client)
{
_client = client;
}
[FunctionName("ProcessEmail")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("Initializing SendGrid ProcessEmail");
var requestBody = new StreamReader(req.Body).ReadToEnd();
var data = JsonConvert.DeserializeObject<EmailContent>(requestBody);
if (data == null)
{
throw new ArgumentNullException("Can't proceed further");
}
var message = new SendGridMessage();
message.AddTo(data.Email);
message.SetFrom(new EmailAddress("ff.com"));
message.AddContent("text/html", HttpUtility.HtmlDecode(data.Body));
message.Subject = data.Subject;
log.LogDebug("Email sent through Send Grid");
await _client.SendEmailAsync(message);
return (ActionResult)new OkObjectResult("Submited sucessfully");
}
public class EmailContent
{
public string? Email { get; set; }
public string? Subject { get; set; }
public string Body { get; set; } = "";
}
}
I am trying to map the response of an api call. What I've done is deserialize the response of the API call (the response is an array) into a model, and then return a new dto using the rootobject's data.
public class RoadStatusService : IRoadStatusService
{
string baseURL = "blah";
private readonly IMapToNew<Road, RoadDto> _mapper;
public RoadStatusService()
{
}
public RoadStatusService(IMapToNew<Road, RoadDto> mapper)
{
_mapper = mapper;
}
public RoadDto GetRoadStatusDetail()
{
var road = CallApi();
return new RoadDto
{
DisplayName = road.Result.DisplayName,
StatusSeverityDescription = road.Result.DisplayName,
StatusSeverity = road.Result.DisplayName
};
}
private async Task<Road> CallApi()
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(baseURL);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage Res = await client.GetAsync(baseURL);
if (Res.IsSuccessStatusCode)
{
var roadResponse = Res.Content.ReadAsStringAsync().Result;
List<Road> road = JsonConvert.DeserializeObject<List<Road>>(roadResponse);
foreach (var item in road)
{
return new Road
{
DisplayName = item.DisplayName,
StatusSeverity = item.StatusSeverity,
StatusSeverityDescription = item.StatusSeverityDescription
};
}
}
return null;
}
}
My question is, how can I use a mapper class to map my model to my dto object without having to do this:
public RoadDto GetRoadStatusDetail()
{
var road = CallApi();
return new RoadDto
{
DisplayName = road.Result.DisplayName,
StatusSeverityDescription = road.Result.DisplayName,
StatusSeverity = road.Result.DisplayName
};
}
I've written a mapperclass and an interface to do this, but I just can't get it to work :
public class RoadToRoadDtoMapper : IMapToNew<Road, RoadDto>
{
public RoadDto Map(Road model)
{
return new RoadDto
{
DisplayName = model?.DisplayName,
StatusSeverity = model?.StatusSeverity,
StatusSeverityDescription = model?.StatusSeverityDescription
};
}
}
and:
public interface IMapToNew<in TIn, out TOut>
{
TOut Map(TIn model);
}
I think the problem I am having is that the api call responds with an array? I am wondering if I should somehow transform my object to a list and then call .Select and use the mapper.map function I have written. I can't get it to work though.
If I understand what you are trying to do, you need a mapper for the lists too and then call the mapper for the objects inside that class:
public class RoadListToRoadListDtoMapper : IMapToNew<List<Road>, List<RoadDto>>
{
private RoadToRoadDtoMapper roadToRoadDtoMapper = new RoadToRoadDtoMapper();
public List<RoadDto> Map(List<Road> models)
{
var roadDtos = new List<RoadDto>();
foreach(var road in models){
roadDtos.Add(roadToRoadDtoMapper.Map(road));
}
return roadDtos;
}
}
I have these methods below that call some SOAP web services, all from the same provider, so they all have the same methods/calls/etc. I'm looking for a more OOP/abstract way to call these without writing so many methods? Ideally I'd like one method for each -> GetClaim(), AddClaim(), SearchClaim(), RemoveClaim(), etc.
Question - Should I pass in the parameters specific to the service to make the method more generic, there by eliminating 15 other methods all like this or is there a better more oop/abstract approach? Can somebody please provide me with an example?
// ex. how can I make these two methods 1?
public async void ClaimSearchForWRG(string url, string userName, string password) {
var client = new WebServiceWRGClient();
var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
var endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<WebServiceWRG>(binding, endpoint);
var webService = channelFactory.CreateChannel();
var user = new User();
user.UserName = await webService.EncryptValueAsync(userName);
user.Password = await webService.EncryptValueAsync(password);
var response = await client.ClaimSearchAsync(user, "", "", 12345, statuscode.NotSet, "");
}
// another call (same provider) with the same call -> ClaimSearchAsync()
public async void ClaimSearchForAWI(string url, string userName, string password) {
var client = new WebServiceAWIClient();
var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
var endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<WebServiceAWI>(binding, endpoint);
var webService = channelFactory.CreateChannel();
var user = new ArmUser();
user.UserName = await webService.EncryptValueAsync(userName);
user.Password = await webService.EncryptValueAsync(password);
var response = await client.ClaimSearchAsync(user, "", "", 12345, ArmStatuscode.NotSet, "");
}
// then we have 15 other web service calls from the same provider for ClaimSearchAsync()
// then we have 15 more calls for ClaimGetAsync()
// then we have 15 more calls for AddClaimAsync()
// then we have 15 more calls for RemoveClaimAsync()
// etc, etc, etc
UPDATED After trying this code below to make things a little more generic (to eliminate redundancy) I'm getting some errors in the code. Specifically related to the compiler not finding the properties associated with the generic entities I'm passing into the method. ex. user.Username is not found -> error message says "'TTwo' does not contain a definition for 'UserName'"
public class Test {
public void TestWebService() {
var ws = new WebService<WebServiceWRG>();
ws.SearchClaim(new WebServiceWRGClient(), new GraceUser(),
"https://trustonline.delawarecpf.com/tows/webservicewrg.svc", "userName", "password");
}
}
public class WebService<T> {
public void SearchClaim<TOne, TTwo>(TOne entity1, TTwo entity2, string url, string userName, string password)
where TOne : class
where TTwo : class
{
var client = entity1;
var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
var endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<T>(binding, endpoint);
var webService = channelFactory.CreateChannel();
var user = entity2;
user.UserName = webService.EncryptValue(userName);
user.Password = webService.EncryptValue(password);
var response = client.ClaimSearch(user, "", "", 12345, GraceStatuscode.NotSet, "");
}
}
UPDATED I was asked to show what "ClaimSearchAsync" does or what it is. I copied this from the web service reference file that was generated from dotnet
System.Threading.Tasks.Task<GRACE_GRACES.WebServiceResult> ClaimSearchAsync(GRACE_GRACES.User user, string ssn, string lastname, int claimnumber, GRACE_GRACES.statuscode statuscode, string assignedto);
as this is a web service, there is no method or code behind that shows what it does.
The provided example methods all violate Single Responsibility Principle (SRP) and Separation of Concerns (SoC) so that is where I started in trying to make them more generic.
The creation of the service and service client should be abstracted out into their own concerns
For example, the web services can be created via a generic factory abstraction
public interface IWebServiceFactory {
TWebService Create<TWebService>(string uri);
}
and simple implementation which encapsulates the creation of the channel factory using the provided URL.
public class ServiceFactory : IWebServiceFactory {
public TWebService Create<TWebService>(string url) {
var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport) {
MaxReceivedMessageSize = Int32.MaxValue,
MaxBufferSize = Int32.MaxValue
};
var endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<TWebService>(binding, endpoint);
TWebService webService = channelFactory.CreateChannel();
return webService;
}
}
The service clients' creation can also be abstracted out into it own concern.
public interface IClientFactory {
TClient Create<TClient>() where TClient : class, new();
}
to be implemented based on the common definition of your clients.
Now for the creation of a generic client you need to take the common functionality expected from the types involved with the member to be invoked.
This can allow for a convention to be used for the expected types. Dynamic expressions were used to construct the conventions applied.
Resulting in the following helpers for the SearchClaimAsync
static class ExpressionHelpers {
public static Func<string, string, TUserResult> CreateUserDelegate<TUserResult>() {
var type = typeof(TUserResult);
var username = type.GetProperty("username", BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public);
var password = type.GetProperty("password", BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public);
//string username =>
var usernameSource = Expression.Parameter(typeof(string), "username");
//string password =>
var passwordSource = Expression.Parameter(typeof(string), "password");
// new TUser();
var user = Expression.New(type);
// new TUser() { UserName = username, Password = password }
var body = Expression.MemberInit(user, bindings: new[] {
Expression.Bind(username, usernameSource),
Expression.Bind(password, passwordSource)
});
// (string username, string password) => new TUser() { UserName = username, Password = password }
var expression = Expression.Lambda<Func<string, string, TUserResult>>(body, usernameSource, passwordSource);
return expression.Compile();
}
public static Func<TService, string, Task<string>> CreateEncryptValueDelegate<TService>() {
// (TService service, string name) => service.EncryptValueAsync(name);
var type = typeof(TService);
// TService service =>
var service = Expression.Parameter(type, "service");
// string name =>
var name = Expression.Parameter(typeof(string), "name");
// service.EncryptValueAsync(name)
var body = Expression.Call(service, type.GetMethod("EncryptValueAsync"), name);
// (TService service, string name) => service.EncryptValueAsync(name);
var expression = Expression.Lambda<Func<TService, string, Task<string>>>(body, service, name);
return expression.Compile();
}
public static Func<TClient, TUser, Task<TResponse>> CreateClaimSearchDelegate<TClient, TUser, TResponse>() {
var type = typeof(TClient);
// TClient client =>
var client = Expression.Parameter(type, "client");
// TUser user =>
var user = Expression.Parameter(typeof(TUser), "user");
var method = type.GetMethod("ClaimSearchAsync");
var enumtype = method.GetParameters()[4].ParameterType; //statuscode
var enumDefault = Activator.CreateInstance(enumtype);
var arguments = new Expression[] {
user,
Expression.Constant(string.Empty), //ssn
Expression.Constant(string.Empty), //lastname
Expression.Constant(12345), //claimnumber
Expression.Constant(enumDefault), //statuscode
Expression.Constant(string.Empty)//assignto
};
// client.ClaimSearchAsync(user, ssn: "", lastname: "", claimnumber: 12345, statuscode: default(enum), assignedto: "");
var body = Expression.Call(client, method, arguments);
// (TClient client, TUser user) => client.ClaimSearchAsync(user,....);
var expression = Expression.Lambda<Func<TClient, TUser, Task<TResponse>>>(body, client, user);
return expression.Compile();
}
}
Take some time to review the comments to get a better understanding of what is being done.
The generic web service can then be defined as follows
public class WebService<TWebServiceClient, TWebService, TUser>
where TWebService : class
where TWebServiceClient : class, new()
where TUser : class, new() {
/// <summary>
/// Create user object model
/// </summary>
private static readonly Func<string, string, TUser> createUser =
ExpressionHelpers.CreateUserDelegate<TUser>();
/// <summary>
/// Encrypt provided value using <see cref="TWebService"/>
/// </summary>
private static readonly Func<TWebService, string, Task<string>> encryptValueAsync =
ExpressionHelpers.CreateEncryptValueDelegate<TWebService>();
private readonly IWebServiceFactory serviceFactory;
private readonly IClientFactory clientFactory;
Lazy<TWebServiceClient> client;
public WebService(IWebServiceFactory serviceFactory, IClientFactory clientFactory) {
this.serviceFactory = serviceFactory ?? throw new ArgumentNullException(nameof(serviceFactory));
this.clientFactory = clientFactory ?? throw new ArgumentNullException(nameof(clientFactory));
client = new Lazy<TWebServiceClient>(() => clientFactory.Create<TWebServiceClient>());
}
public async Task<TResponse> SearchClaimAsync<TResponse>(WebServiceOptions options) {
TWebService webService = serviceFactory.Create<TWebService>(options.URL);
TUser user = createUser(
await encryptValueAsync(webService, options.UserName),
await encryptValueAsync(webService, options.Password)
);
Func<TWebServiceClient, TUser, Task<TResponse>> claimSearchAsync =
ExpressionHelpers.CreateClaimSearchDelegate<TWebServiceClient, TUser, TResponse>();
TResponse response = await claimSearchAsync.Invoke(client.Value, user);
return response;
}
//...other generic members to be done
}
public class WebServiceOptions {
public string URL { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
The code it self is decoupled enough from implementation concerns to allow for it to be tested in isolation to ensure that it behaves as expected.
As demonstrated in the following unit tested
[TestClass]
public class GenericWebServiceTests {
[TestMethod]
public void Should_Create_New_WebService() {
//Arrange
var serviceFactory = Mock.Of<IWebServiceFactory>();
var clientFactory = Mock.Of<IClientFactory>();
//Act
var actual = new WebService<WebServiceWRGClient, IWebService, User1>(serviceFactory, clientFactory);
//Assert
actual.Should().NotBeNull();
}
[TestMethod]
public async Task Should_ClaimSearchAsync() {
//Arrange
var service = Mock.Of<IWebService>();
Mock.Get(service)
.Setup(_ => _.EncryptValueAsync(It.IsAny<string>()))
.ReturnsAsync((string s) => s);
var serviceFactory = Mock.Of<IWebServiceFactory>();
Mock.Get(serviceFactory)
.Setup(_ => _.Create<IWebService>(It.IsAny<string>()))
.Returns(service);
var clientFactory = Mock.Of<IClientFactory>();
Mock.Get(clientFactory)
.Setup(_ => _.Create<WebServiceWRGClient>())
.Returns(() => new WebServiceWRGClient());
string url = "url";
string username = "username";
string password = "password";
var options = new WebServiceOptions {
URL = url,
UserName = username,
Password = password
};
var webService = new WebService<WebServiceWRGClient, IWebService, User1>(serviceFactory, clientFactory);
//Act
var actual = await webService.SearchClaimAsync<WebServiceResult>(options);
//Assert
//Mock.Get(serviceFactory).Verify(_ => _.Create<IService1>(url));
//Mock.Get(service).Verify(_ => _.EncryptValue(username));
//Mock.Get(service).Verify(_ => _.EncryptValue(password));
//Mock.Get(clientFactory).Verify(_ => _.Create<Client1>());
actual.Should().NotBeNull();
}
#region Support
public class User1 {
public string UserName { get; set; }
public string Password { get; set; }
}
public class User2 {
public string UserName { get; set; }
public string Password { get; set; }
}
public class WebServiceWRGClient {
public Task<WebServiceResult> ClaimSearchAsync(User1 user, string ssn, string lastname, int claimnumber, statuscode statuscode, string assignedto) {
return Task.FromResult(new WebServiceResult());
}
}
public enum statuscode {
NotSet = 0,
}
public class Client2 { }
public interface IWebService {
Task<string> EncryptValueAsync(string value);
}
public interface IService2 {
Task<string> EncryptValueAsync(string value);
}
public class Service1 : IWebService {
public Task<string> EncryptValueAsync(string value) {
return Task.FromResult(value);
}
}
public class WebServiceResult {
}
#endregion
}
This should be enough to get you started in reviewing the other members to be made generic. The above provided code has been tested and works as expected based on what was provided in the original question.
Do note that this does seem like a large task depending on the amount of members to be refactored. You should take some time to make sure the effort is even worth it.
You have classical Divergent Change smell here.
Signs and Symptoms. You find yourself having to change many unrelated methods when you make changes to a class. For example, when adding a new product type you have to change the methods for finding, displaying, and ordering products.
I suggest to make refactoring to Abstract Factory pattern. You will separate web service and object creation logic.
Abstract Factory is a creational design pattern that lets you produce families of related objects without specifying their concrete classes.
So you will have something like:
And some code:
public interface IFactory
{
Client CreateClient();
User CreateUser();
Channel CreateChannel(BasicHttpBinding binding, EndpointAddress endpoint);
}
abstract public class AbstractFactory<T> : IFactory
{
public abstract Client CreateClient()
public abstract User CreateUser();
public Channel CreateChannel(BasicHttpBinding binding, EndpointAddress endpoint)
{
var channelFactory = new ChannelFactory<T>(binding, endpoint);
return channelFactory.CreateChannel();
}
}
public class AWIFactory : AbstractFactory<WebServiceAWI>
{
public override Client CreateClient()
{
return new WebServiceAWIClient();
}
public override User CreateUser()
{
return new ArmUser();
}
}
public class WRGFactory : AbstractFactory<WebServiceWRG>
{
public override Client CreateClient()
{
return new WebServiceWRGClient();
}
public override User CreateUser()
{
return new User();
}
}
public class WebService
{
private readonly IFactory _factory;
public WebService(IFactory factory)
{
_factory = factory;
}
public async void ClaimSearchAsync(string url, string userName, string password)
{
var client = _factory.CreateClient();
var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
var endpoint = new EndpointAddress(url);
var channel = _factory.CreateChannel(binding, endpoint);
var user = _factory.CreateUser();
user.UserName = await channel.EncryptValueAsync(userName);
user.Password = await channel.EncryptValueAsync(password);
var response = await client.ClaimSearchAsync(user, "", "", 12345, statusCode, "");
}
...
}
And here is how you create WebService:
var wrgWebService = new WebService(new WRGFactory());
I did something similar when I had several different soap endpoints, where every endpoint had some types that were completely the same, only with a different class name. Automatically generated classes contain the partial modifier, which enables you to add additional logic to the generated class.
In your case:
"'TTwo' does not contain a definition for 'UserName'"
You have to create an interface that contains a property Username and a property Password:
public interface IUser {
string UserName { get; }
string Password { get; }
}
public partial User : IUser { } //must be in the correct namespace for partial to work
public partial ArmUser : IUser { } //must be in the correct namespace for partial to work
public class Test {
public void TestWebService() {
var ws = new WebService<WebServiceWRG>();
ws.SearchClaim(new WebServiceWRGClient(), new GraceUser(),
"https://trustonline.delawarecpf.com/tows/webservicewrg.svc", "userName", "password");
}
}
public class WebService<T> {
public void SearchClaim<TOne, TTwo>(TOne entity1, TTwo entity2, string url, string userName, string password)
where TOne : class
where TTwo : IUser // limits the TTwo class to implement IUser
{
var client = entity1;
var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
var endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<T>(binding, endpoint);
var webService = channelFactory.CreateChannel();
var user = entity2;
user.UserName = webService.EncryptValue(userName);
user.Password = webService.EncryptValue(password);
var response = client.ClaimSearch(user, "", "", 12345, GraceStatuscode.NotSet, "");
}
}
Instead of passing TTwo, you can then also add the new modifier to the TTwo condition (where T : TTwo, new()), then you can generate an instance of TTwo inside the SearchClaim function, which would make it look like the following:
public interface IUser {
string UserName { get; set; }
string Password { get; set; }
}
public partial User : IUser { } //must be in the correct namespace for partial to work
public partial ArmUser : IUser { } //must be in the correct namespace for partial to work
public class Test {
public void TestWebService() {
var ws = new WebService<WebServiceWRG>();
ws.SearchClaim(new WebServiceWRGClient(), new GraceUser(),
"https://trustonline.delawarecpf.com/tows/webservicewrg.svc", "userName", "password");
}
}
public class WebService<T> {
public void SearchClaim<TOne, TTwo>(TOne entity1, string url, string userName, string password)
where TOne : class
where TTwo : IUser, new() // limits the TTwo class to implement IUser
{
var client = entity1;
var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
var endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<T>(binding, endpoint);
var webService = channelFactory.CreateChannel();
var user = new TTwo();
user.UserName = webService.EncryptValue(userName);
user.Password = webService.EncryptValue(password);
var response = client.ClaimSearch(user, "", "", 12345, GraceStatuscode.NotSet, "");
}
}
You might also have to make some interface for your TOne, but you should be able to figure that out on your own.
Not sure how is your code structure, but I'll just focus on the provided sample.
From what I've seen, if this method and the other related methods are used in different classes, I would suggest to create a class that will handle it, and then use this class instead of the methods. But if this method and other related methods are used in a specific class, I would recommend to create a generic methods that will substitute the redundant methods. You'll need to compare all related methods first, and get the common numerator between them, make this a start point for your generic approach.
Here is untested example (based on what I understood from your sample) :
public class CallWebService<T> // don't forget to inherit IDisposal.
{
private WebServiceWRGClient Client {get; set;}
private BasicHttpBinding HttpBinding {get; set;}
private EndpointAddress Endpoint {get; set;}
private ChannelFactory Channel {get; set;}
// if needed outside this class, make it public to be accessed globally.
private User UserAccount {get; set;}
public CallWebService<T>(string url)
{
Client = new WebServiceWRGClient();
//See which Binding is the default and use it in this constructor.
HttpBinding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
Endpoint = new EndpointAddress(url);
// T is generic, WebServiceWRG in this example
Channel = new ChannelFactory<T>(HttpBinding, Endpoint).CreateChannel();
UserAccount = new User();
}
// another constructor with BasicHttpBinding
public CallWebService<T>(string url, BasicHttpSecurityMode securityMode)
{
Client = new WebServiceWRGClient();
//See which Binding is the default and use it in this constructor.
HttpBinding = new BasicHttpBinding(securityMode);
Endpoint = new EndpointAddress(url);
// T is generic, WebServiceWRG in this example
Channel = new ChannelFactory<T>(HttpBinding, Endpoint).CreateChannel();
UserAccount = new User();
}
// Change this method to return the response. Task<Response> is just a placeholder for this example
public async Task<Response> Call(string userName, string password)
{
UserAccount.UserName = await Channel.EncryptValueAsync(userName);
UserAccount.Password = await Channel.EncryptValueAsync(password);
var response = await Client.ClaimSearchAsync(User, "", "", 12345, statuscode.NotSet, "");
}
/*
[To-Do] : gather all other releated methods into this class, then try to simplify them.
*/
}
You can also configure the constructors as needed, for instance, you can make constructors that takes WebServiceWRGClient and BasicHttpBinding ..etc. So, it's more open to you.
You could do similar approach if it'll be used across the project, but if it's only used in one class, then you could do something like this :
// Configure it as needed, but avoid using `void` with async, as the exceptions in sync and async methods handled differently.
// Also, try to make sense here, make the method return the results.
public async Task CallWebService<T>(WebServiceWRGClient client, string url, string userName, string password)
{
var channelFactory = new ChannelFactory<T>(new BasicHttpBinding(BasicHttpSecurityMode.Transport, new EndpointAddress(url)).CreateChannel();
var user = new User(); // coming from service reference
user.UserName = await channelFactory.EncryptValueAsync(userName);
user.Password = await channelFactory.EncryptValueAsync(password);
var response = await client.ClaimSearchAsync(user, "", "", 12345, statuscode.NotSet, "");
}
I am having a simple controller which needs to be unit tested not integration tested. I just need a way to mock so that I can verify if receive method is called. We already have test against Receive(), so no need to verify what is going inside that method.
My code looks like
public class MessageController : Controller
{
private readonly ConnectionDetail connectionDetail;
private readonly QueueDetail queueDetail;
public MessageController(IOptions<ConnectionDetail> connectionDetail, IOptions<QueueDetail> queueDetail)
{
this.connectionDetail = connectionDetail.Value;
this.queueDetail = queueDetail.Value;
}
[HttpGet()]
public IActionResult Get()
{
try
{
var channel = CreateConnectionAndChannel(queueDetail);
var message = channel.Receive();
var hbaseKey = new HbaseKey { Key = new Guid(message) };
return Ok(hbaseKey);
}
catch
{
return StatusCode(500, "Exception occured while processing. Try again.");
}
}
private IChannel CreateConnectionAndChannel(QueueDetail queueDetail)
{
var factory = new Factory();
var adapter = factory.Connect(MessagingType.MQ, connectionDetail);
return adapter.BindQueue(queueDetail);
}
}
Refactor the CreateConnectionAndChannel function out into its own service
public interface IChannelProvider {
IChannel CreateConnectionAndChannel();
}
and have controller explicitly depend on that service
public class MessageController : Controller {
private readonly IChannelProvider channelProvider;
public MessageController(IChannelProvider channelProvider) {
this.channelProvider = channelProvider;
}
[HttpGet()]
public IActionResult Get() {
try {
var channel = channelProvider.CreateConnectionAndChannel();
var message = channel.Receive();
var hbaseKey = new HbaseKey { Key = new Guid(message) };
return Ok(hbaseKey);
} catch {
return StatusCode(500, "Exception occured while processing. Try again.");
}
}
}
So now only the IChannelProvider needs to be mocked to test the controller in isolation.
I just need a way to mock so that I can verify if receive method is called.
public void Verify_Received_Called() {
//Arrange
var channel = new Mock<IChannel>();
channel
.Setup(_ => _.Receive())
.Returns("My mock value here");
var mockProvider = new Mock<IChannelProvider>();
mockProvider.Setup(_ => _.CreateConnectionAndChannel())
.Returns(channel.Object);
var controller = new MessageController(mockProvider.Object);
//Act
var result = controller.Get();
//Assert
channel.Verify(_ => _.Receive(), Times.AtLeastOnce);
}
The provider implementation could look like...
public class ChannelProvider : IChannelProvider {
private readonly ConnectionDetail connectionDetail;
private readonly QueueDetail queueDetail;
public ChannelProvider(IOptions<ConnectionDetail> connectionDetail, IOptions<QueueDetail> queueDetail) {
this.connectionDetail = connectionDetail.Value;
this.queueDetail = queueDetail.Value;
}
public IChannel CreateConnectionAndChannel() {
var factory = new Factory();
var adapter = factory.Connect(MessagingType.MQ, connectionDetail);
return adapter.BindQueue(queueDetail);
}
}
In order to do this, you need to move your CreateConnectionAndChannel method to a separate dependency, for instance, ChannelFactory which implements IChannelFactory interface.
public interface IChannelFactory {
IChannel CreateConnectionAndChannel(QueueDetail queueDetail);
}
public class ChannelFactory : IChannelFactory {
public IChannel CreateConnectionAndChannel(QueueDetail queueDetail)
{
var factory = new Factory();
var adapter = factory.Connect(MessagingType.MQ, connectionDetail);
return adapter.BindQueue(queueDetail);
}
}
public class MessageController : Controller
{
private readonly ConnectionDetail connectionDetail;
private readonly QueueDetail queueDetail;
private readonly IChannelFactory channelFactory;
public MessageController(IOptions<ConnectionDetail> connectionDetail, IOptions<QueueDetail> queueDetail, IChannelFactory channelFactory)
{
this.connectionDetail = connectionDetail.Value;
this.queueDetail = queueDetail.Value;
this.channelFactory = channelFactory;
}
[HttpGet()]
public IActionResult Get()
{
try
{
var channel = channelFactory.CreateConnectionAndChannel(queueDetail);
var message = channel.Receive();
var hbaseKey = new HbaseKey { Key = new Guid(message) };
return Ok(hbaseKey);
}
catch
{
return StatusCode(500, "Exception occured while processing. Try again.");
}
}
}
After that you can mock your controller in test (using Moq for example):
[TestFixture]
public class TestMessageController
{
[Test]
public void TestGet()
{
var channelMock = new Mock<IChannel>(MockBehavior.Strict);
channelMock
.Setup(c => c.Receive())
.Returns(null);
var channelFactoryMock = new Mock<IChannelFactory>(MockBehavior.Strict);
channelFactory
.Setup(cf => cf.CreateConnectionAndChannel(It.IsAny<IOptions<QueueDetail>>()))
.Returns();
var controller = new MessageController(null, null, channelFactoryMock.Object);
controller.Get();
}
}