Home > Enterprise >  Acess two different lists with the same type of objects in unit testing
Acess two different lists with the same type of objects in unit testing

Time:10-06

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.

  • Related