Search code examples
c#asp.net-corenunittempdata

TempData does not work in my .NET Core project


I have very simple ASP.NET Core project. This is my controller:

public class AdminController : Controller
{
    private readonly IProductRepository _repository;

    public AdminController(IProductRepository repository)
    {
        _repository = repository;
    }

    public ViewResult Index()
    {
        return View(_repository.Products.OrderBy(p => p.Id));
    }

    public ViewResult Edit(int id)
    {
        Product product = _repository.Products.FirstOrDefault(p => p.Id == id);

        return View(product);
    }

    [HttpPost]
    public ActionResult Edit(Product product)
    {
        if (ModelState.IsValid)
        {
            _repository.SaveProduct(product);
            TempData["message"] =  $"{product.Name} has been saved";

            return RedirectToAction("Index");
        }

        return View(product);
    }
}

And my view _AdminLayout.cshtml

<main role="main" class="pb-3">
    <div>
        @if (TempData["message"] != null)
        {
            <div class="alert alert-success">
                @TempData["message"]
            </div>
        }
    </div>
    @RenderBody()
</main>

And finally Index.cshtml view

@model System.Collections.Generic.IEnumerable<Domains.Entities.Product>

@{
    ViewBag.Title = "Admin";
    Layout = "_AdminLayout";
}

<div>
// some cshtml code ...
</div>

As you can see, I am waiting the following business logic:

  1. Save a product;

  2. Redirect to Index.cs admin view

  3. I see message about successfully saved product on the top of my screen.

But nothing happened. I don't see the message after saving and TempData is null after redirect to Index.

And, additionally, I have a problem with unit test of this method.

Here is my test code:

[Test]
public void Can_Save_Valid_Changes()
{
    Mock<IProductRepository> mock = new Mock<IProductRepository>();

    AdminController controller = new AdminController(mock.Object);

    Product product = new Product { Name = "Test"};

    IActionResult result = controller.Edit(product);

    // check if have saved product to repo 
    mock.Verify(m => m.SaveProduct(product));

    Assert.IsNotInstanceOf<ViewResult>(result);
}

I use NUnit library for testing. After running the test I have the following message from test: object reference not set to an instance of an object. It seems putting value to TempData throws this exception during the test.

Tell me please, what is happening with this TempData? Where is the mistake? I had seen a lot of questions about strange behavior of TempData and I did not found an answer for me. I appreciate any help.


Solution

  • When unit testing a controller that accesses TempData you need to create a TempDataDictionary for the controller

    [Test]
    public void Can_Save_Valid_Changes() {
        //Arrange
        var httpContext = new DefaultHttpContext();
        var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>());
        var mock = new Mock<IProductRepository>();
        AdminController controller = new AdminController(mock.Object);
        controller.TempData = tempData;
    
        Product product = new Product { Name = "Test"};
    
        //Act
        IActionResult result = controller.Edit(product);
    
        //Assert
        // check if have saved product to repo 
        mock.Verify(m => m.SaveProduct(product));
        Assert.IsNotInstanceOf<ViewResult>(result);
        Assert.IsTrue(tempData.ContainsKey("message"));
    }