简体   繁体   中英

Can't get Moq Setup to not return null

I'm trying to Setup a Mock call to a the IModelFactory interface that I have.

here is the IModelFactory interface

public interface IModelFactory
    {

       MaterialAcceptedModel Create(MaterialAccepted model);                                           
       MaterialAccepted Parse(MaterialAcceptedModel model);        
  }

This is the ModelFactory class which implements the IModelFactor interface (I've only add here the method I'm trying to test, no need to add the implementation of the Create Method)

public class ModelFactory : IModelFactory
    {
        private UrlHelper _urlHelper;
        private IRTWRepository _repo;
        //private IKeysGeneratorService _keysGen;
        private IGeocodeService _geocoder;

        public ModelFactory(HttpRequestMessage request, IRTWRepository repo,IGeocodeService geocoder)
        {
            _urlHelper = new UrlHelper(request);
            _repo = repo;
            _geocoder = geocoder;
        }                


        #region Parses

        public MaterialAccepted Parse(MaterialAcceptedModel model)
        {
            try
            {
                if (!string.IsNullOrWhiteSpace(model.category))
                {
                    var category = _repo.CategoryRepository.Get(model.category);

                    if (category == null) return null;

                    var entry = new MaterialAccepted()
                    {
                        business = model.business,
                        businessService = model.businessService,
                        residential = model.residential,
                        residentialService = model.residentialService,
                        note = model.note,
                        Category = category
                    };
                    return entry;
                }
                return null;
            }
            catch
            {
                return null;
            }
        }

        #endregion
    }

I'm using a BaseAPiController that contains the repo and configuration Interfaces

public class BaseApiController : ApiController
    {
        IRTWRepository _repository;
        IModelFactory _modelFactory;
        IConfiguration _configuration;
        IGeocodeService _geocoder;

        public BaseApiController(IRTWRepository repository,IConfiguration configuration)
        {
            _repository = repository;
            _configuration = configuration;
        }

        protected IRTWRepository TheRepository
        {
            get
            {
                return _repository;
            }
        }
        protected IConfiguration TheConfiguration
        {
            get
            {
                return _configuration;
            }
        }

        protected IModelFactory TheModelFactory
        {                              
            get
            {                
                _geocoder = new GeocodeService(_configuration.GetValue("geocodeGoogleApiKey"));

                if (_modelFactory == null)
                {
                    _modelFactory = new ModelFactory(this.Request, _repository,_geocoder);
                }
                return _modelFactory;
            }
        }

Here is the action method in the controller I'm trying to test

        [HttpPost]
        [Route("api/recyclecenters/{rcid}/materials/")]
        public IHttpActionResult Post(int rcid, [FromBody]MaterialAcceptedModel model)
        {
            try
            {
                if (model != null)
                {
                    var recycleCenter = TheRepository.RecycleCenterRepository.Get(rcid);

                    if (recycleCenter == null)
                        return NotFound();

                    if (!ModelState.IsValid)
                        return BadRequest(ModelState);

                    var entity = TheModelFactory.Parse(model);

                    if (entity == null) return BadRequest("Could not read material accepted in body");


                    if (TheRepository.MaterialAcceptedRepository.Get(recycleCenter.RecycleCenterId, entity.Category.name) != null)
                        return Conflict();

                    recycleCenter.Materials.Add(entity);

                    if (TheRepository.SaveAll())
                    {
                        string locationHeader = Url.Link("Materials", new { rcid = rcid, name = model.category.ToLower() });
                        return Created<MaterialAcceptedModel>(locationHeader, TheModelFactory.Create(entity));
                    }
                    return BadRequest("Could not save to the database");
                }
                return BadRequest();
            }
            catch (Exception ex)
            {
                return BadRequest(ex.Message);
            }

        }

This is the line that returns null even though I mocked it up on my test method

  var entity = TheModelFactory.Parse(model);

and this one is my TestClass

namespace API.Tests.Web
{
    [TestClass]
    public class MaterialsControllerTest
    {        
        private Mock<IRTWRepository> repository;
        private Mock<IModelFactory> factory;
        private Mock<IConfiguration> configuration;
        private Mock<IRTWAPIIdentityService> identityService;
        private MaterialsController controller;

        RecycleCenter recycleCenter;        

        private MaterialAccepted CreateMaterial()
        {
            return new MaterialAccepted()
            {
                business = true,
                businessService = EnumRecycleCenterService.Dropoff,
                residential = false,
                residentialService = EnumRecycleCenterService.Pickup,
                note = "this a note",
                Category = new Category()
                {
                    name = "Books"
                }
            };
        }

        [TestInitialize]
        public void Initialize()
        {
            repository = new Mock<IRTWRepository>();
            factory = new Mock<IModelFactory>();
            configuration = new Mock<IConfiguration>();
            identityService = new Mock<IRTWAPIIdentityService>();
            controller = new MaterialsController(repository.Object,configuration.Object);
            controller.Request = new HttpRequestMessage();            
            recycleCenter = new RecycleCenter(){RecycleCenterId = 1};            
        }



        [TestMethod]
        public void Post_ShouldReturnConflictIfTheRecycleCenterAlreadyTakesMaterial()
        {

            //arrange
            repository.Setup(r => r.RecycleCenterRepository.Get(It.IsAny<int>())).Returns(() => recycleCenter);
            factory.Setup(f => f.Parse(new MaterialAcceptedModel())).Returns(() => new MaterialAccepted());
            configuration.Setup(c => c.GetValue(It.IsAny<string>())).Returns(() => "APIKEY");

            repository.Setup(r => r.MaterialAcceptedRepository.Get(It.IsAny<int>(), It.IsAny<string>())).Returns(() => null);       

            //act
            var actionResult = controller.Post(It.IsAny<int>(),new MaterialAcceptedModel());

            //assert
            Assert.IsInstanceOfType(actionResult, typeof(ConflictResult));
        }

    }
}

This is the line that's not working because it always return null instead of a new instance of MaterialAccepted

 factory.Setup(f => f.Parse(new MaterialAcceptedModel())).Returns(() => new MaterialAccepted());

I tried f.Parse(It.IsAny()) but still doesn't work.

To clarify

the above line of code is returning null because is not mocking the f.Parse() call, instead is executing it and returning null because the if condition I have on that method

Anyone could explain why the Setup is not working?

Setting up your Mock using It.IsAny will work:

factory.Setup(f => f.Parse(It.IsAny<MaterialAcceptedModel>()))
       .Returns(() => new MaterialAccepted());

However, as has been said by @Macilquham, I can't see where your are passing the Mock to your controller in the supplied code so that it is used by the production code.

If you don't call the method on your Mock object, which you don't, you're currently calling the method on the instance of the real object created by your base class, then it doesn't matter how you set up your mock it's not going to work. If you are able to change your base class, then doing something like this would allow you to work around your problem:

// Add defaulted parameter to base class to allow modelFactory creation
// to be overridden/injected (this will prevent the current default behaviour
// when fetching the factory, if a non-null is passed in)
public BaseApiController(IRTWRepository repository,IConfiguration configuration, 
                         IModelFactory modelFactory = null)
{
    _modelFactory = modelFactory;
}

Modify your sut constructor to allow you to supply a modelFactory (again, default it to null), then amend your test as appropriate:

controller = new MaterialsController(repository.Object,configuration.Object,
                                     factory.Object);

You don't seem to be injecting in the IModelFactory into the controller. You need to make sure that your production code is using the Mock you are setting up in the test.

Mock cannot return null directly. The trick is just to create a null object. Assuming the object returned is of type class Material :

    Material nullMaterial = null;
    ...
    repository.Setup(r => r.MaterialAcceptedRepository
      .Get(It.IsAny<int>(), It.IsAny<string>()))
      .Returns(nullMaterial);  

This should solve your problem

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM