无法将最小起订量设置设置为不 return null

Can't get Moq Setup to not return null

我正在尝试设置对我拥有的 IModelFactory 接口的模拟调用。

这里是 IModelFactory 接口

public interface IModelFactory
    {

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

这是实现了 IModelFactor 接口的 ModelFactory class (我在这里只添加了我要测试的方法,不需要添加 Create 方法的实现)

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
    }

我正在使用包含存储库和配置接口的 BaseAPIController

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;
            }
        }

这是我要测试的控制器中的操作方法

        [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);
            }

        }

这是 return 为空的行,即使我在我的测试方法中对其进行了模拟

  var entity = TheModelFactory.Parse(model);

这是我的 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));
        }

    }
}

这是行不通的行,因为它总是 return null 而不是 MaterialAccepted

的新实例
 factory.Setup(f => f.Parse(new MaterialAcceptedModel())).Returns(() => new MaterialAccepted());

我试过 f.Parse(It.IsAny()) 但还是不行。

澄清

上面的代码行是 returning null 因为没有模拟 f.Parse() 调用,而是执行它并且 returning null 因为我有 if 条件在那个方法上

谁能解释为什么安装程序不起作用?

Mock 不能直接return null。 诀窍就是创建一个空对象。 假设对象 returned 的类型是 class Material:

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

这应该可以解决您的问题

您似乎没有将 IModelFactory 注入控制器。您需要确保您的生产代码正在使用您在测试中设置的 Mock。

使用 It.IsAny 设置 Mock 将有效:

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

但是,正如@Macilquham 所说,我看不到您在提供的代码中将 Mock 传递给控制器​​的位置,以便生产代码使用它。

如果您没有在 Mock 对象上调用该方法(实际上您没有),那么您当前是在由您的基 class 创建的真实对象的实例上调用该方法,那么它不管你如何设置你的模拟,它都不会起作用。如果你能够改变你的基础 class,那么做这样的事情可以解决你的问题:

// 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;
}

修改您的 sut 构造函数以允许您提供 modelFactory(同样,将其默认为 null),然后适当地修改您的测试:

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