سؤال

Scenario: I am learning how to unit test. Currently am working on tests for an mvc action method with nUnit and FakeItEasy. I have a test to verify that the method throws an exception if passed an id that doesn't exist. The action method calls a repository wrapper method for .Single(), which will throw an exception if nothing is found. This is good.

In my test, I do the following:

  • Create fake IRepository using FakeItEasy
  • Create test data
  • Configure .Single() wrapper method to get data from my test data

Problem: I am having issues testing this. The problem is that when passed an invalid id, an exception is thrown right in the configuration code for the fake repository, instead of in the action method itself. The reason why is obvious. The configuration code is ran before the action method gets executed, and the configuration code calls .Single() on the test data... which (intentionally of course) does not contain the invalid id. So it throws an exception right then and there, and never even makes it to the action method. What I am not sure about, is how to get around this. The exception needs to be thrown inside the action method. I don't know how to configure the return value in a way that avoids this conundrum.

Code:
Controller Code

        public ViewResult Details(int id)
        {
            var dbPart = _repository
                             .GetSingleRecord<Part>(x => x.PartID == id);

            var viewmodel = new DetailsViewModel()
            {
                PartID = dbPart.PartID
            };

            return View(viewmodel);
        }


Test Code

        [TestFixtureSetUp]
        public void TestFixtureSetUp()
        {
            // Create a fake PartID that exists
            partID_that_exists = 1;

            // Create a fake PartID that doesn't exist
            partID_that_doesnt_exist = -100;
        }

        [Test]
        public void an_exception_is_thrown_if_the_part_doesnt_exist()
        {
            // Arrange
            FakeRepository.FakePartID = partID_that_doesnt_exist;
            _fakeRepository = FakeRepository.Create();
            _controller = new PartController(_fakeRepository);

            // Act & Assert
            Assert.Throws<InvalidOperationException>(() => 
                         _controller.Details(partID_that_doesnt_exist));
        }


Fake Repository Code

        public class FakeRepository
        {
            public static int? FakePartID { get; set; }              

            public static IBasicRepository Create()
            {
                // Create fake repository
                var fakeRepository = A.Fake<IBasicRepository>();

                // Create fake test data
                var fakeParts = new List<Part>()
                {
                    new Part() 
                    { 
                        PartID = 1, PartDesc = "Fake Part 1"
                    },
                    new Part() 
                    { 
                        PartID = 2, PartDesc = "Fake Part 2"
                    }
                };

                // Configure fake repository to return fake data
                A.CallTo(() => fakeRepository.GetAllRecords<Part>())
                                             .Returns(fakeParts);
                if (FakePartID.HasValue)
                {
                    /* BELOW CODE IS THE PROBLEM */
                    A.CallTo(fakeRepository)
                   .Where(call => call.Method.Name == "GetSingleRecord")
                   .WithReturnType<Part>()
                   .Returns(fakeParts.Single(x => x.PartID == FakePartID));
                }

                // Return the newly created & configured fakeRepository
                return fakeRepository;
            }
        }
هل كانت مفيدة؟

المحلول

I figured it out. I needed to use ReturnsLazily() instead of Returns().

ReturnsLazily delays setting the method's return values until the method is actually called, instead of setting them when the method's configuration code is executed.

New, Working Code:

A.CallTo(fakeRepository)
 .Where(call => call.Method.Name == "GetSingleRecord")
 .WithReturnType<Part>()
 .ReturnsLazily(() => fakeParts
                       .Single(x => x.PartID == FakePartID));
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top