I'm trying to write an Unit Test for my ASP.Net Core application with XUnit framework and MOQ and am trying to test the below method(snippet given below):
public async Task<IActionResult> Save([FromBody] DTO.ContactUs contactUs)
{
contactUs.FirstName = _htmlEncoder.Encode(contactUs.FirstName);
contactUs.LastName = _htmlEncoder.Encode(contactUs.LastName);
contactUs.EmailAddress = _htmlEncoder.Encode(contactUs.EmailAddress);
contactUs.Phone = _htmlEncoder.Encode(contactUs.Phone);
if (HttpContext.User.CurrentClient() != null)
contactUs.ClientId = HttpContext.User.CurrentClient().ClientId;
contactUs.UserId = User.GetUserId();
string dbName = HttpContext.User.CurrentClient().ConnectionString;
var result = _clientService.AddNewContactUs(contactUs, dbName);
if (result)
{
try
{
int clientId = HttpContext.User.CurrentClient().ClientId;
var clientDetails = _clientService.GetClientDetailsByClientID(clientId);
// Lines of code...
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
return Json(result);
}
While I can mock all the other dependent services, I'm kind of stuck with the HttpContext part. I am not able to mock the HttpContext.User.CurrentClient() part where HttpContext.User is of type ClaimsPrincipal and CurrentClient is an user-defined function, defined as:
public static Client CurrentClient(this ClaimsPrincipal principal)
{
if (!string.IsNullOrEmpty(principal.Claims.Single(p => p.Type.Equals(AppClaimTypes.CurrentClient)).Value))
{
int clientId = Convert.ToInt32(principal.Claims.Single(p => p.Type.Equals(AppClaimTypes.CurrentClient)).Value);
return principal.GetClients().Where(c => c.ClientId == clientId).FirstOrDefault();
}
else
{
return null;
}
}
This is my UnitTest class that I have managed to write till now:
public class ContactUsControllerTests
{
private Mock<IClientService> clientServiceMock;
private Mock<IWebHostEnvironment> webHostEnvironmentMock;
private Mock<HtmlEncoder> htmlEncoderObjMock;
private Mock<IEmailNotification> emailNotificationMock;
private Mock<HttpContext> mockContext;
private Mock<HttpRequest> mockRequest;
private Mock<ClaimsPrincipal> mockClaimsPrincipal;
private ContactUs contactUsObj = new ContactUs()
{
FirstName = "TestFN",
LastName = "TestLN",
EmailAddress = "testemail#gmail.com",
Phone = "4564560000",
Comments = "This is just a test"
};
private ClaimsPrincipal principal = new ClaimsPrincipal();
public ContactUsControllerTests()
{
clientServiceMock = new Mock<IClientService>();
webHostEnvironmentMock = new Mock<IWebHostEnvironment>();
htmlEncoderObjMock = new Mock<HtmlEncoder>();
emailNotificationMock = new Mock<IEmailNotification>();
mockRequest = new Mock<HttpRequest>();
mockContext = new Mock<HttpContext>();
// set-up htmlEncoderMock
htmlEncoderObjMock.Setup(h => h.Encode(contactUsObj.FirstName)).Returns(contactUsObj.FirstName);
htmlEncoderObjMock.Setup(h => h.Encode(contactUsObj.LastName)).Returns(contactUsObj.LastName);
htmlEncoderObjMock.Setup(h => h.Encode(contactUsObj.EmailAddress)).Returns(contactUsObj.EmailAddress);
htmlEncoderObjMock.Setup(h => h.Encode(contactUsObj.Phone)).Returns(contactUsObj.Phone);
htmlEncoderObjMock.Setup(h => h.Encode(contactUsObj.Comments)).Returns(contactUsObj.Comments);
// set-up mockContext
mockContext.Setup(m => m.Request).Returns(mockRequest.Object);
mockContext.Object.User.CurrentClient().ClientId = 30; // this throws error
//other initialisations
}
[Fact]
public async void SaveMethodTest()
{
ContactUsController contactUsControllerObj = new ContactUsController(clientServiceMock.Object, webHostEnvironmentMock.Object, htmlEncoderObjMock.Object, emailNotificationMock.Object);
// Act
await contactUsControllerObj.Save(contactUsObj);
// Arrange
// Lines of code
}
}
Any help whatsoever on this would very helpful.
Related
I have a service name "DataAccessService". In that service we are calling Procedure and other methods from AppDbContext.
I am writing Unit Test cases for DataAccessService methods. We have method called IsDatabaseUp() which will give response if connection with databases success or not.
When I am trying to access AppDbContext is null which is breaking the unit test method.
DataAccessService:
public bool IsDatabaseUp()
{
try
{
int counter = 1;
while (counter <= EnvConstants.DatabaseCounter)
{
try
{
using (var scope = _serviceScopeFactory.CreateScope())
{
AppDbContext dbContext = scope.ServiceProvider.GetService<AppDbContext>();
//Here dbContext is null
bool isDbUp = dbContext.IsDatabaseUp;
if (isDbUp)
return isDbUp;
counter++;
}
}
catch (Exception ex)
{
counter++;
}
}
}
catch (Exception ex)
{
}
return false;
}
DataServiceTests:
private readonly IDataAccessService _dataAccessService;
private readonly Mock<IServiceScopeFactory> _serviceScopeFactory;
public DataAccessServiceTest()
{
//Arrange
var serviceProvider = new Mock<IServiceProvider>();
serviceProvider
.Setup(x => x.GetService(typeof(AppDbContext)));
var serviceScope = new Mock<IServiceScope>();
serviceScope.Setup(x => x.ServiceProvider).Returns(serviceProvider.Object);
_serviceScopeFactory = new Mock<IServiceScopeFactory>();
_serviceScopeFactory
.Setup(x => x.CreateScope())
.Returns(serviceScope.Object);
serviceProvider
.Setup(x => x.GetService(typeof(IServiceScopeFactory)))
.Returns(_serviceScopeFactory.Object);
var serviceCollection = new ServiceCollection();
serviceCollection.AddMemoryCache();
var serviceProvider1 = serviceCollection.BuildServiceProvider();
var memoryCache = serviceProvider1.GetService<IMemoryCache>();
var inMemoryCahce= new InMemoryCahce(memoryCache);
_dataAccessService = new DataAccessService(_serviceScopeFactory.Object);
_cacheService = new ServiceOfCahe(inMemoryCahce, _dataAccessService);
LoadMemoryData();
}
[Fact]
public void GetMasterDataInfromationFromSql_Test()
{
var testResult = _dataAccessService.IsDatabaseUp();
}
Can you please help me on this.
Line var job = _mapper.Map<DataAccess.Domain.Lab.Job>(jobViewModel); gives error
job variable is always returning null while running the unit test
i have added the mapping for Jobs Profile `
Below is the code:
JobTest.cs class
public class JobTests
{
private static Mock<IMapper> _mapper;
public JobTests()
{
if (_mapper == null)
{
_mapper = new Mock<IMapper>();
_mapper.Setup(x => x.ConfigurationProvider.AssertConfigurationIsValid());
_mapper.Setup(x => x.ConfigurationProvider)
.Returns(
() => new MapperConfiguration(
cfg =>
{
cfg.AddProfile<JobsProfile>();
//cfg.CreateMap<AddJobCommand,JobsProfile > ();
//cfg.CreateMap<JobViewModel, AddJobCommand>();
//cfg.CreateMap<AddJobCommand,JobViewModel>();
}));
}
}
[Fact]
public async Task AddJob_AddSingleEntry()
{
var mapperMock = new Mock<IMapper>();
var data = JobData.AddFakeJobList();
var mockSet = FakeDbSetup.GetMockDbSet<DataAccess.Domain.Lab.Job>(data);
var mockContext = FakeDbSetup.GetMockDbContext();
mockSet.Setup(x => x.AsNoTracking()).Returns(mockSet.Object);
mockContext.Setup(c => c.Jobs).Returns(mockSet.Object);
AddJobCommandHandler handler = new AddJobCommandHandler(mockContext.Object, _mapper.Object);
JobViewModel vm= JobData.AddFakeJobList2();
AddJobCommand command = new AddJobCommand(vm);
//var stubScheduleCommand = new Mock<AddJobCommand>(mockContext.Object);
var job = await handler.Handle(command, new System.Threading.CancellationToken());
Assert.NotNull(job);
}
}
AddJobCommand.cs
public class AddJobCommandHandler : IRequestHandler<AddJobCommand, JobViewModel>
{
private readonly IDrillingFluidsContext _context;
private readonly IMapper _mapper;
public AddJobCommandHandler(IDrillingFluidsContext context, IMapper mapper)
{
(_context, _mapper) = (context, mapper);
}
public async Task<JobViewModel> Handle(AddJobCommand command, CancellationToken cancellationToken)
{
if (command.JobViewModel == null) throw new InvalidOperationException("Empty request.");
var jobViewModel = command.JobViewModel;
try
{
var job = _mapper.Map<DataAccess.Domain.Lab.Job>(jobViewModel);
_context.Set<DataAccess.Domain.Lab.Job>().Add(job);
if (job.Notes!= null)
{
var newNote = job.Notes.FirstOrDefault(n => n.IsNew);
if (newNote != null)
{
newNote.JobId = job.Id;
_context.Set<DataAccess.Domain.Lab.JobNote>().Attach(newNote);
_context.Entry(newNote).State = EntityState.Added;
}
}
if (string.IsNullOrWhiteSpace(job.Name))
{
job.Name = await GenerateJobName(job);
}
await _context.SaveChangesAsync();
jobViewModel.Id = job.Id;
return jobViewModel;
}
catch (DbEntityValidationException e)
{
foreach (var eve in e.EntityValidationErrors)
{
Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
eve.Entry.Entity.GetType().Name, eve.Entry.State);
foreach (var ve in eve.ValidationErrors)
{
Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
ve.PropertyName, ve.ErrorMessage);
}
}
throw;
}
catch (DbUpdateException e)
{
throw;
}
catch (Exception e)
{
throw;
}
}
I want the JobViewModel data to be added to the the variable jobs.But it always returns null.This works fine when i am trying to call this method via PostMan.
The problem is that the code uses a mocked mapper that has no setup for the Map method. If there is no setup, the method will return null (for
MockBehavior.Loose`).
If you want the Map method to return a value, you need add a setup, e.g.:
[Fact]
public async Task AddJob_AddSingleEntry()
{
var data = JobData.AddFakeJobList();
var mockSet = FakeDbSetup.GetMockDbSet<DataAccess.Domain.Lab.Job>(data);
var mockContext = FakeDbSetup.GetMockDbContext();
mockSet.Setup(x => x.AsNoTracking()).Returns(mockSet.Object);
mockContext.Setup(c => c.Jobs).Returns(mockSet.Object);
AddJobCommandHandler handler = new AddJobCommandHandler(mockContext.Object, _mapper.Object);
JobViewModel vm= JobData.AddFakeJobList2();
AddJobCommand command = new AddJobCommand(vm);
//var stubScheduleCommand = new Mock<AddJobCommand>(mockContext.Object);
var job = new DataAccess.Domain.Lab.Job()
{
// Initialize job as needed
};
_mapper.Setup(x => x.Map<DataAccess.Domain.Lab.Job>(vm))
.Returns(job);
var job = await handler.Handle(command, new System.Threading.CancellationToken());
Assert.NotNull(job);
}
In the constructor it is not necessary to add setups for the configuration provider if you are using a mocked mapper. So you can setup the _mapper simply like this:
public JobTests()
{
_mapper = new Mock<IMapper>();
}
So, I have a scenario where I have implemented my own JWT authentication scheme and is the default authentication and challenge scheme in my Startup.cs:
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
//code ommitted for brevity
})
.AddCookie("cookie")
.AddOpenIdConnect("facbook", async options =>
{
options.ResponseType = "code";
options.SignInScheme = "cookie";
//code ommitted for brevity
});
As you can see above that I have added AddOpenIdConnect and AddCookie for my external authentication. Now my question is that if I have a Redirect ActionMethod like this, how can return the Challenge scheme to point to my external one (facebook):
[HttpGet]
public async Task<IActionResult> Redirect()
{
var result = await HttpContext.AuthenticateAsync();
if (result.Succeeded)
{
return RedirectToAction("Index");
}
return Challenge("facebook");
}
This would also mean that my AuthenticateAsync would not work in this case since the default authentication scheme is pointing to JWT.
How can I add a this to my Challenge request and AuthenticateAsync method?
Thanks
To return the login page for your custom Identity Provider, you need to call the SignInManager.ConfigureExternalAuthenticationProperties() method. This method lets you define the redirectUrl and provider (you called your provider "facebook").
I have written it like this:
[Controller]
[Route("web/v2/[controller]")]
public class AccountController : Controller
{
private IAccountService accountService;
public AccountController(IAccountService accountService)
{
this.accountService = accountService;
}
// GET: web/Account/connect/{provider}
[AllowAnonymous]
[HttpGet("connect/{medium}/{provider}", Name = "web-v2-account-external-connect-challenge")]
public async Task<ActionResult> ExternalLogin([FromRoute]string medium, [FromRoute]string provider)
{
//var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Account", new { medium, provider });
var redirectUrl = Url.RouteUrl("web-v2-account-external-connect-callback", new { medium, provider });
var properties = await accountService.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return Challenge(properties, provider);
}
// GET: web/Account/connect/{provider}/callback
[HttpGet("connect/{medium}/{provider}/callback", Name = "web-v2-account-external-connect-callback")]
public async Task<ActionResult> ExternalLoginCallback([FromRoute]string medium, [FromRoute]string provider)
{
try
{
var login_result = await accountService.PerfromExternalLogin();
if (login_result.Status)
{
var model = new LoginResultVM
{
Status = true,
Medium = medium,
Platform = login_result.Platform
};
return View(model);
}
else
{
var model = new LoginResultVM
{
Status = false,
Medium = medium,
Platform = login_result.Platform,
Error = login_result.Error,
ErrorDescription = login_result.ErrorDescription
};
return View(model);
}
}
catch (OtherAccountException otherAccountEx)
{
var model = new LoginResultVM
{
Status = false,
Medium = medium,
Platform = provider,
Error = "Could not login",
ErrorDescription = otherAccountEx.Message
};
return View(model);
}
catch (Exception ex)
{
var model = new LoginResultVM
{
Status = false,
Medium = medium,
Platform = provider,
Error = "Could not login",
ErrorDescription = "There was an error with your social login"
};
return View(model);
}
}
}
While my AccountRepository looks like this:
internal interface IAccountRepository
{
...
}
internal class AccountRepository : IAccountRepository
{
private MintPlayerContext mintplayer_context;
private UserManager<Entities.User> user_manager;
private SignInManager<Entities.User> signin_manager;
private JwtIssuerOptions jwtIssuerOptions;
public AccountRepository(UserManager<Entities.User> user_manager, SignInManager<Entities.User> signin_manager, MintPlayerContext mintplayer_context, IOptions<JwtIssuerOptions> jwtIssuerOptions)
{
this.user_manager = user_manager;
this.signin_manager = signin_manager;
this.mintplayer_context = mintplayer_context;
this.jwtIssuerOptions = jwtIssuerOptions.Value;
}
public async Task<Tuple<User, string>> Register(User user, string password)
{
...
}
public async Task<LoginResult> LocalLogin(string email, string password, bool createCookie)
{
...
}
public async Task<IEnumerable<AuthenticationScheme>> GetExternalLoginProviders()
{
var providers = await signin_manager.GetExternalAuthenticationSchemesAsync();
return providers.ToList();
}
public Task<AuthenticationProperties> ConfigureExternalAuthenticationProperties(string provider, string redirectUrl)
{
var properties = signin_manager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return Task.FromResult(properties);
}
public async Task<LoginResult> PerfromExternalLogin()
{
var info = await signin_manager.GetExternalLoginInfoAsync();
if (info == null)
throw new UnauthorizedAccessException();
var user = await user_manager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
if (user == null)
{
string username = info.Principal.FindFirstValue(ClaimTypes.Name);
string email = info.Principal.FindFirstValue(ClaimTypes.Email);
var new_user = new Entities.User
{
UserName = username,
Email = email,
PictureUrl = null
};
var id_result = await user_manager.CreateAsync(new_user);
if (id_result.Succeeded)
{
user = new_user;
}
else
{
// User creation failed, probably because the email address is already present in the database
if (id_result.Errors.Any(e => e.Code == "DuplicateEmail"))
{
var existing = await user_manager.FindByEmailAsync(email);
var existing_logins = await user_manager.GetLoginsAsync(existing);
if (existing_logins.Any())
{
throw new OtherAccountException(existing_logins);
}
else
{
throw new Exception("Could not create account from social profile");
}
}
else
{
throw new Exception("Could not create account from social profile");
}
}
await user_manager.AddLoginAsync(user, new UserLoginInfo(info.LoginProvider, info.ProviderKey, info.ProviderDisplayName));
}
await signin_manager.SignInAsync(user, true);
return new LoginResult
{
Status = true,
Platform = info.LoginProvider,
User = ToDto(user)
};
}
public async Task<IEnumerable<UserLoginInfo>> GetExternalLogins(ClaimsPrincipal userProperty)
{
...
}
public async Task AddExternalLogin(ClaimsPrincipal userProperty)
{
...
}
public async Task RemoveExternalLogin(ClaimsPrincipal userProperty, string provider)
{
...
}
public async Task<User> GetCurrentUser(ClaimsPrincipal userProperty)
{
var user = await user_manager.GetUserAsync(userProperty);
return ToDto(user);
}
public async Task Logout()
{
await signin_manager.SignOutAsync();
}
#region Helper methods
private string CreateToken(Entities.User user)
{
...
}
#endregion
#region Conversion methods
internal static Entities.User ToEntity(User user)
{
...
}
internal static User ToDto(Entities.User user)
{
...
}
#endregion
}
New to Unit Testing web api.
I am writing a Unit test to Test a controller and I have to mock Iconfiguration. The appsettings,json has a section called "AppSettings", I'm trying to mock it.
Also, the mock.setup returns null value in the controller causing it to fail.
Here is my controller:
private readonly ILogger _logger;
private readonly IConfiguration _configuration;
private readonly ICarPairingTable PairingTable;
private readonly ICarDealerSettingsTable DealerSettingsTable;
static AppSettings appSettings = null;
public CarController(IConfiguration configuration, ICarPairingTable carPairingTable, ICarDealerSettingsTable settingsTable)
{
_configuration = configuration;
appSettings = configuration.Get<AppSettingsModel>().AppSettings;
PairingTable = carPairingTable;
DealerSettingsTable = settingsTable;
}
[HttpGet]
public ActionResult Get(string id){
string DealerId ="";
DealerId = PairingTable.GetDealerId(id).Result;
if (string.IsNullOrEmpty(DealerId))
{
result = new ReturnResult
{
status = "Fail",
data = "ID is invalid"
};
return NotFound(result);
}
SettingsInfo info = DealerSettingsTable.GetSettingsInfo(DealerId).Result;
if (info == null)
{
result = new ReturnResult
{
status = "Fail",
data = "Not Found"
};
return NotFound(result);
}
result = new ReturnResult
{
status = "Success",
data = info
};
return Ok(result);
}
Here is my Unit Test:
[Fact]
public void Test1()
{
var mockConfig = new Mock<IConfiguration>();
var configurationSection = new Mock<IConfigurationSection>();
configurationSection.Setup(a => a.Value).Returns("testvalue");
mockConfig.Setup(a => a.GetSection("AppSettings")).Returns(configurationSection.Object);
var mock1 = new Mock<ICarPairingTable>();
mock1.Setup(p => p.GetDealerId("456")).ReturnsAsync("123");
var mock2 = new Mock<ICarDealerSettingsTable>();
SettingsInfo mockSettings = new SettingsInfo()
{
DealerId = "123",
Name="Dealer1"
};
mock2.Setup(p => p.GetSettingsInfo("123")).ReturnsAsync(()=>mockSettings);
CarController controller = new CarController(mockConfig.Object, mock1.Object, mock2.Object);
var result = controller.Get("456");
//Dont know what to assert
}
Wrote unit test, but not sure if my approach is correct, Help will be appreciated.
This is more of a design issue wrapped in an XY problem.
Really should not be injecting IConfiguration. Based on how the configuration is using by the controller what you should have done was register the settings with the service collection in startup
Startup.ConfigureServices
//...
AppSettings appSettings = Configuration.Get<AppSettingsModel>().AppSettings;
services.AddSingleton(appSettings);
//...
and explicitly inject the settings into the controller
//...
private readonly AppSettings appSettings = null;
public CarController(AppSettings appSettings , ICarPairingTable carPairingTable, ICarDealerSettingsTable settingsTable) {
this.appSettings = appSettings;
PairingTable = carPairingTable;
DealerSettingsTable = settingsTable;
}
//...
So now when unit testing the controller in isolation, you can initialize an instance of the desired class and provide when exercising the unit test.
Reference Explicit Dependencies Principle
You also appear to be mixing async-await and blocking calls like .Result.
I sugest you make the action async all the way
[HttpGet]
public async Task<ActionResult> Get(string id){
string DealerId = await PairingTable.GetDealerId(id);
if (string.IsNullOrEmpty(DealerId)) {
var result = new ReturnResult {
status = "Fail",
data = "ID is invalid"
};
return NotFound(result);
}
SettingsInfo info = await DealerSettingsTable.GetSettingsInfo(DealerId);
if (info == null) {
var result = new ReturnResult {
status = "Fail",
data = "Not Found"
};
return NotFound(result);
}
var result = new ReturnResult {
status = "Success",
data = info
};
return Ok(result);
}
Reference Async/Await - Best Practices in Asynchronous Programming
That way the unit test can finally be arranged correctly to verify the expected behavior
[Fact]
public async Task Should_Return_Ok_ReturnRsult() {
//Arrange
var id = "456";
var dealerId = "123";
SettingsInfo expected = new SettingsInfo() {
DealerId = dealerId,
Name="Dealer1"
};
var pairingMock = new Mock<ICarPairingTable>();
pairingMock.Setup(p => p.GetDealerId(id)).ReturnsAsync(dealerId);
var dealerSettingsMock = new Mock<ICarDealerSettingsTable>();
dealerSettingsMock.Setup(p => p.GetSettingsInfo(dealerId)).ReturnsAsync(() => expected);
CarController controller = new CarController(new AppSettings(), pairingMock.Object, dealerSettingsMock.Object);
//Act
var actionResult = await controller.Get(id);
var actual = actionResult as OkObjectResult;
//Assert (using FluentAssertions)
actual.Should().NotBeNull();
actual.Value.Should().BeOfType<ReturnResult>();
var actualResult = actual.Value as ReturnResult;
actualResult.data.Should().BeEquivalentTo(expected);
}
I'm writing a unit test where I'm trying to partially mock a service. What I mean is I want one of the methods of the service to return a different mocked object and another method to behave as normal. This is the method I'm testing:
public async Task<List<string>> GetDeletedRecordIds<T>(DateTime startDate)
where T : ISalesForceObject
{
List<string> result;
try
{
var client = await this.GetForceClient();
var init = await client.GetDeleted<DeletedRecordRootObject>(typeof(T).Name, startDate, DateTime.Now);
result = init?.DeletedRecords.Select(d => d.Id).ToList();
}
catch (Exception e)
{
this._logger.LogError(LoggingEvents.GENERAL_ERROR, e, "GetDeletedRecordIds");
throw;
}
return result;
}
This is the method that I need to return a mocked object:
public async Task<IForceClient> GetForceClient()
{
ForceClient forceClient = null;
try
{
var auth = new AuthenticationClient();
var consumerKey = this._settingService.GetSetting("SalesForceConsumerKey");
var consumerSecret = this._settingService.GetSetting("SalesForceConsumerSecret");
var password = this._settingService.GetSetting("SalesForcePassword");
var securityToken = this._settingService.GetSetting("SalesForceSecurityToken");
var username = this._settingService.GetSetting("SalesForceUsername");
var tokenUrl = $"{this._settingService.GetSetting("SalesForceUrl")}/services/oauth2/token";
await auth.UsernamePasswordAsync(
consumerKey,
consumerSecret,
username,
password + securityToken,
tokenUrl);
forceClient = new ForceClient(auth.InstanceUrl, auth.AccessToken, auth.ApiVersion);
}
catch (Exception e)
{
this._logger.LogError(LoggingEvents.GENERAL_ERROR, e, $"GetForceClient");
throw;
}
return forceClient;
}
And this is what I currently have in my unit test:
var mockForceClient = new Mock<IForceClient>();
mockForceClient
.Setup(
i => i.GetDeleted<DeletedRecordRootObject>(
It.IsAny<string>(),
It.IsAny<DateTime>(),
It.IsAny<DateTime>())).ReturnsAsync(deletedRecordRootObject);
var mockService = new Mock<IForceDotComService>();
mockService.Setup(m => m.GetDeletedRecordIds<sf.Account>(It.IsAny<DateTime>()))
.Returns(async (DateTime d) => await this._service.GetDeletedRecordIds<sf.Account>(d));
mockService.Setup(m => m.GetForceClient())
.ReturnsAsync(mockForceClient.Object);
Currently, the test runs in GetDeletedRecordIds until it hits the call to the GetForceClient method. Then instead of returning the mocked ForceClient object, it actually tries to run the method which of course fails.
Thanks in advance for any help.
SOLUTION:
Here's how I solved my problem.
First, I created a service to return the ForceClient as follows:
public class ForceClientService : IForceClientService
{
private readonly ILogger _logger;
private readonly ISettingService _settingService;
public ForceClientService(
ILogger<ForceClientService> logger,
ISettingService settingService)
{
this._logger = logger;
this._settingService = settingService;
}
public async Task<IForceClient> GetForceClient()
{
ForceClient forceClient = null;
try
{
var auth = new AuthenticationClient();
var consumerKey = this._settingService.GetSetting("SalesForceConsumerKey");
var consumerSecret = this._settingService.GetSetting("SalesForceConsumerSecret");
var password = this._settingService.GetSetting("SalesForcePassword");
var securityToken = this._settingService.GetSetting("SalesForceSecurityToken");
var username = this._settingService.GetSetting("SalesForceUsername");
var tokenUrl = $"{this._settingService.GetSetting("SalesForceUrl")}/services/oauth2/token";
await auth.UsernamePasswordAsync(
consumerKey,
consumerSecret,
username,
password + securityToken,
tokenUrl);
forceClient = new ForceClient(auth.InstanceUrl, auth.AccessToken, auth.ApiVersion);
}
catch (Exception e)
{
this._logger.LogError(LoggingEvents.GENERAL_ERROR, e, $"GetForceClient");
throw;
}
return forceClient;
}
}
Then I changed the method I am testing:
public async Task DeleteRecord<TSf>(TSf record)
where TSf : ISalesForceObject
{
try
{
var client = await this._forceClientService.GetForceClient();
var response = await client.DeleteAsync(typeof(TSf).Name, record.Id);
if (!response)
{
throw new Exception($"Error deleting record with ID {record.Id}");
}
}
catch (Exception e)
{
this._logger.LogError(LoggingEvents.GENERAL_ERROR, e, $"ForceDotComService.DeleteRecord");
throw;
}
}
Then I rebuilt my mock to mock the dependencies vs. the methods:
var mockForceClient = new Mock<IForceClient>();
mockForceClient
.Setup(
i => i.GetDeleted<DeletedRecordRootObject>(
It.IsAny<string>(),
It.IsAny<DateTime>(),
It.IsAny<DateTime>())).ReturnsAsync(deletedRecordRootObject);
var mockLogger = new Mock<ILogger<ForceDotComService>>();
var mockForceClientService = new Mock<IForceClientService>();
mockForceClientService.Setup(m => m.GetForceClient()).ReturnsAsync(mockForceClient.Object);
this._service = new ForceDotComService(mockLogger.Object, mockForceClientService.Object);
It is now working as expected. Thanks so much for the help!
Extract this.GetForceClient() out into its own service backed by an abstraction
public IForceClientProvider {
Task<IForceClient> GetForceClient();
}
you would then refactor your current class under test to explicitly depend on that interface via constructor injection.
public class ForceDotComService : IForceDotComService {
private readonly IForceClientProvider provider;
public ForceDotComService(IForceClientProvider provider) {
this.provider = provider;
}
public async Task<List<string>> GetDeletedRecordIds<T>(DateTime startDate)
where T : ISalesForceObject {
List<string> result;
try {
var client = await provider.GetForceClient();
var init = await client.GetDeleted<DeletedRecordRootObject>(typeof(T).Name, startDate, DateTime.Now);
result = init?.DeletedRecords.Select(d => d.Id).ToList();
} catch (Exception e) {
this._logger.LogError(LoggingEvents.GENERAL_ERROR, e, "GetDeletedRecordIds");
throw;
}
return result;
}
}
This would then allow you to mock the desired behavior when testing. In implementation code you would have the same code presented above in the GetForceClient() method.
You mock dependencies, not methods on the class under test.
You need to inject the dependency IForceClient, for example by making it a constructor parameter. Because now your GetForceClient() is simply being called on the class under test, which runs in that class and not on your mock, and so simply returns the new ForceClient() stated in there.