locked
Id property as protected and its problems RRS feed

  • Question

  • User1236824203 posted

    In my app, all domain classes follow the standardization:

    1. All implement the interface IEntity
    2. Id properties are protected*
    3. The properties of type IList are protected and initialized in the constructor.

    Below is a classic example of a domain entity:

    public class CheckListItemTemplate : IEntity
    {
        public virtual int Id { get; protected set; }
        public virtual string Text { get; set; }
        public virtual CheckListItemTemplate Parent { get; set; }
        public virtual IList<CheckListItemTemplate> Itens { get; protected set; }
    
        public CheckListItemTemplate()
        {
            Itens = new List<CheckListItemTemplate>();
        }
    
        public void AddItem(CheckListItemTemplate item)
        {
            item.Parent = this;
            Itens.Add(item);
        }
    }

    *This is because the id is generated by the database and not run the risk of some developer trying to set this property.

    Test project

    We have a fake generic repository used in the tests:

    public class Repository<T> : IRepository<T>
        where T : class, IEntity
    {
        private readonly IDictionary<int, T> _context = new Dictionary<int, T>();
    
        public void Delete(T obj)
        {
            _context.Remove(obj.Id);
        }
    
        public void Store(T obj)
        {
            if (obj.Id > 0)
                _context[obj.Id] = obj;
            else
            {
                var generateId = _context.Values.Any() ? _context.Values.Max(p => p.Id) + 1 : 1;
                var stub = Mock.Get<T>(obj);
                stub.Setup(s => s.Id).Returns(generateId);
                _context.Add(generateId, stub.Object);
            }
        }
    
        // .. 
    }

    As you can see in the Store*, all test objects (of type IEntity) should be a Mock**. This is because in UI project, when we save an object NHibernate updating the property Id. In testing project we have to do this manually, and we have no way to set the property Id with a new value, so the solution was mock the entire object to the Get property Id correspond to the new Id . Exactly what does this line stub.Setup(s => s.Id).Returns(generateId).

    *By convention, objects with Id <= 0 are new and Id> 0 are existing objects in the database.
    **For Mock I use Moq.

    Id as protected

    The biggest problem occurs because of Id property and the fact that is protected. When we talk about the designer, is a great approach but this brings huge inconvenience when we test our application.

    For example, in a test that I'm writing I need my Fake repository with some data already populated.

    Code

    Follow me. I have the following classes (+ CheckListItemTemplate shown above.)

    public class Passo : IEntity
    {
        public int Id { get; protected set; }
        public virtual IList<CheckListItemTemplate> CheckListItens { get; protected set; }
    }
    
    public class Processo : IEntity
    {
        public virtual int Id { get; protected set; }
        public virtual Passo Passo { get; set; }
        public virtual IList<CheckListItem> CheckListItens { get; protected set; }
    }

    After saving the Processo, the first Passo is associated with the Processo: (sorted by Ordem field following field CreateAt)

    model.Passo = PassoRepositorio.All().OrderBy(p => p.Ordem).ThenBy(p => p.CreateAt).First();
    model.CheckListItens.Clear();
    Parallel.ForEach(Mapper.Map<IList<CheckListItem>>(model.Passo.CheckListItens), (it) => model.AddCheckListItem(it));

    This code is running whenever you save a new Processo. For any test that creates a new Processo, this code will be executed!

    Test

    If we have to create a test that creates a new Processo, our first goal is to populate the PassoRepositorio repository with some dummy data*, with Passos and CheckListItemTemplates specifically for the above code does not fail**.

    *To populate objects with dummy data I use AutoFixture.
    ** Will fail if no Passo is found in the repository .First() and this Passo has no checklist Mapper.Map(model.Passo.CheckListItens).


    So we need a repository of Passos and each Passo with a list of CheckListItens. Remember that every object IEntity should be an Mock<> so we can mock property Id

    First attempt

    First configure my TestInitialize to populate my repository with some dummy data:

    var fix = new Fixture();
    var listPassos = fix.Build<Mock<Passo>>()
                                .Do((passo) => {
                                    passo.SetupProperty(x => x.Nome, fix.Create<string>());
                                    passo.SetupGet(x => x.CheckListItens).Returns(
                                        fix.Build<CheckListItemTemplate>() // Needs to a Mock<>
                                            .With(p => p.Texto)
                                            .OmitAutoProperties()
                                            .CreateMany(5).ToList()
                                        );
                                })
                                .OmitAutoProperties()
                                .CreateMany(10);
    
    foreach (var item in listPassos)
        passoRepository.Store(item.Object);    

    Then I can run the tests:

    [TestMethod]
    public void Salvar_novo_processo_modificar_data_atendimento_passo_atual()
    {
        // Arrange
        var fix = new Fixture();
        var vm = fix.Create<ProcessoViewModel>();
    
        //Act
        Controller.salvar(vm); // Problem here. (For convert ProcessoViewModel to Processo I use a AutoMaper. In repository needs destination to be a Mock<Processo>
        var processo = Repository.Get(p => p.DataEntrada == vm.DataEntrada && p.ProximoAtendimento == vm.ProximoAtendimento);
    
        //Asserts
        processo.Should().NotBeNull();
        processo.Passo.Should().NotBeNull();
    }

    Questions

    We create a list of 10 Passo where each Passo is actually is a Mock<>, great! But:

    1. For each Passo have a list of 5 'Mock<CheckListItens>' items, and each Id should be 1, 2, 3, 4 and 5 (in that order). How to achieve this? How to obtain this list of IList<Mock<>> inside a Mock<> with Id already filled? That is, the configuration passo.SetupGet(x => x.CheckListItens).Returns( ???

    2. The responsible for creating objects in my controller, basically uses AutoMapper to convert my ViewModel object to an object that can be persisted Model in my repository: model = Mapper.Map<TModel>(vm);
      The problem is that my repository Fake can not save an object IEntity, just Mock<IEntity>. How to configure AutoMapper to always return a Mock<>?

    Sunday, June 2, 2013 9:43 AM

Answers

  • User-488622176 posted

    There is a standard feature that solves your issue:

    This allows you to call the "protected" setter from your test projects, but prohibits it from being called from other assemblies.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Monday, June 3, 2013 8:18 AM
  • User-488622176 posted

    NHibernate does require public setters. 

    For your interface question : NHibernate does require virtual properties. They do work with interfaces as interfaces require properties to be declared on the object that implements the interface

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Wednesday, June 5, 2013 5:44 PM

All replies

  • User-488622176 posted

    There is a standard feature that solves your issue:

    This allows you to call the "protected" setter from your test projects, but prohibits it from being called from other assemblies.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Monday, June 3, 2013 8:18 AM
  • User1236824203 posted

    Very interesting .. I think it will help me. Just one question, how NHibernate will work with internal setter?

    The Nhibernate creates a proxy to be able to set this property, as internal do not know if it will set. Know me know this?

    Monday, June 3, 2013 1:08 PM
  • User1236824203 posted

    There is another problem. In my Fake repository I depend a interface and not a concrete class.

    public interface IEntity
    {
    	int Id { get; }
    }
    
    public class Repository<T> : IRepository<T>
    	where T : class, IEntity
    {
    	public void Store(T obj)
    	{
    		if (obj.Id > 0)
    			_context[obj.Id] = obj;
    		else
    		{
    			var generateId = _context.Values.Any() ? _context.Values.Max(p => p.Id) + 1 : 1;
    			var stub = Mock.Get<T>(obj);
    			stub.Setup(s => s.Id).Returns(generateId);
    			_context.Add(generateId, stub.Object);
    		}
    	}
    }


    So I can not do something like:

    obj.Id = generateId;
    _context.Add(generateId, obj);

    Even with this attribute.

    Monday, June 3, 2013 1:44 PM
  • User-488622176 posted

    NHibernate does require public setters. 

    For your interface question : NHibernate does require virtual properties. They do work with interfaces as interfaces require properties to be declared on the object that implements the interface

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Wednesday, June 5, 2013 5:44 PM
  • User-525215917 posted

    NHibernate is also able to access private fields if you say so in mapping files.

    Thursday, June 6, 2013 7:12 PM
  • User-488622176 posted

    Correct. It can, as shown here https://github.com/jagregory/fluent-nhibernate/wiki/Mapping-private-properties, https://groups.google.com/forum/#!topic/nhusers/wiH1DPGOhgU,  or here http://ayende.com/blog/3936/nhibernate-mapping-property

    It wasn't possible in older versions, but it appears they added this.

    Monday, June 10, 2013 4:05 PM