If I can explain my problem, these are two methods, a test method and its target:
Test method
[Fact]
public async Task Create_Post_Valid_Id_Valid_Name()
{
#region Arrange
var model = new FixedShippingScheduleEditViewModel{Id = 10, Name = "Schedule"};
var schedules = new List<Hyper360FixedShippingScheduleSummaryModel>()
{new Hyper360FixedShippingScheduleSummaryModel{Id = 11, Name = "AnotherSchedule"}};
var refreshedSchedules = new List<Hyper360FixedShippingScheduleSummaryModel>()
{new Hyper360FixedShippingScheduleSummaryModel{Id = 10, Name = "Schedule"}};
_shippingApiClient.Setup(x => x.GetAllFixedSchedules(CancellationToken.None)).Returns(Task.FromResult((IList<Hyper360FixedShippingScheduleSummaryModel>)schedules));
_shippingApiClient.Setup(x => x.CreateFixedSchedule(It.Is<FixedShippingScheduleEditModel>(s => s.Name == "Schedule"), CancellationToken.None)).Returns(Task.CompletedTask);
_shippingApiClient.Setup(x => x.GetAllFixedSchedules(CancellationToken.None)).Returns(Task.FromResult((IList<Hyper360FixedShippingScheduleSummaryModel>)refreshedSchedules));
_localizationService.Setup(x => x.GetResource("Bizay.Logistic.Admin.Schedules.Notification.Create.Error")).Returns("Success");
_notificationService.Setup(x => x.SuccessNotification("Success", true));
#endregion
#region Act
var result = await _scheduleController.Create(model, true, CancellationToken.None);
#endregion
#region Assert
result.Should().BeOfType<ViewResult>();
((ViewResult)result).Model.Should().BeOfType<FixedShippingScheduleEditViewModel>();
((FixedShippingScheduleEditViewModel)((ViewResult)result).Model).Name.Should().Be("AnotherSchedule");
((FixedShippingScheduleEditViewModel)((ViewResult)result).Model).Id.Should().Be(11);
#endregion
}
Tested method
[HttpPost]
[ValidateAntiForgeryToken]
[HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")]
public async Task<ActionResult> Create(FixedShippingScheduleEditViewModel model, bool continueEditing, CancellationToken cancellationToken)
{
var schedules = await _shippingApiClient.GetAllFixedSchedules(cancellationToken);
if (schedules.Any(s => s.Name.Equals(model.Name, StringComparison.InvariantCultureIgnoreCase)))
{
ModelState.AddModelError(nameof(model.Name), _localizationService.GetResource("Bizay.Logistic.Admin.Schedules.Error.Name.Repeated"));
}
if (ModelState.IsValid)
{
await _shippingApiClient.CreateFixedSchedule(new FixedShippingScheduleEditModel { Name = model.Name }, cancellationToken);
var refreshedSchedules = await _shippingApiClient.GetAllFixedSchedules(cancellationToken);
var schedule = refreshedSchedules?.FirstOrDefault(s => s.Name == model.Name);
if (schedule == null)
{
_notificationService.ErrorNotification(_localizationService.GetResource("Bizay.Logistic.Admin.Schedules.Notification.Create.Error"));
return View(model);
}
_notificationService.SuccessNotification(_localizationService.GetResource("Bizay.Logistic.Admin.Schedules.Notification.Create.Success"));
if (continueEditing)
{
return RedirectToAction("Edit", new { id = schedule.Id });
}
else
{
return RedirectToAction("List");
}
}
return View(model);
}
What happens here is that schedules.Any(s => s.Name.Equals(model.Name, StringComparison.InvariantCultureIgnoreCase))
should be working on the schedules
list whereas the refreshedSchedules?.FirstOrDefault(s => s.Name == model.Name)
should be working on the refreshedSchedules
list, the problem is that is that both lambdas are acting on the schedules
list, any idea how I could make this work?
CodePudding user response:
Assuming _shippingApiClient
is a Moq Mock<ShippingApiClient>
, you want to set up a Callback
that populates a list that later calls to GetAllFixedSchedules()
will fulfill:
//Arrnge
int id = 10;
string expectedName = "Schedule";
var viewModel = new FixedShippingScheduleEditViewModel { Name = expectedName };
var scheduleList = new List<Hyper360FixedShippingScheduleSummaryModel>()
{
new Hyper360FixedShippingScheduleSummaryModel { Id = id , Name = "AnotherSchedule" } //Id = 10
};
_shippingApiClient
.Setup(x => x.CreateFixedSchedule(It.IsAny<FixedShippingScheduleEditModel>(), It.IsAny<CancellationToken>()))
.Callback((FixedShippingScheduleEditModel model, CancellationToken token) => {
// TODO: perhaps some asserts here
model.Id = id; //Since id is being asserted
scheduleList.Add(new Hyper360FixedShippingScheduleSummaryModel
{
Id = model.Id,
Name = model.Name
});
})
.Returns(Task.CompletedTask);
_shippingApiClient
.Setup(x => x.GetAllFixedSchedules(It.IsAny<CancellationToken>()))
.ReturnsAsync(() => scheduleList);
//...code omitted for brevity
//Act
var result = await _scheduleController.Create(viewModel, true, CancellationToken.None);
//Assert
result.Should().BeOfType<RedirectToActionResult>();
RedirectToActionResult redirect = (RedirectToActionResult) result;
redirect.ActionName.Should().Be("Edit");
redirect.RouteValues["id"].Should().Be(id);
With callbacks, you can set up a mock with some sort of (locally kept) state.
Alternatively, set up sequential calls, see Different return values the first and second time with Moq, where you first return the initial list, then the list with the added item, but that is brittle - if you add another call in between, results will be off.