Search code examples
c#design-patternstddmockingmvp

Resolution for Model View Presenter Testing... Do I use DTO's or Domain objects or both?


The basic issue is how to test a presenter.

Take: Domain object (will eventually be persisted to DB) Base attributes are Id (DB ID, Int/GUID/Whatever) and TransientID (Local ID until saved, GUID)

DomainObject


namespace domain {  
  public class DomainObject {    
   private int _id;    
   private Guid transientId;    
   public DomainObject()
   {      
    _transient_Id = Guid.NewGuid();    
   }
  }
}

PresenterTest:


var repository = Mock.StrictMock();
var view = Mock.StrictMock();

view.Save += null;
var saveEvent = LastCall.Ignore().GetEventRaiser();

var domainObject = new DomainObject() {Id = 0, Attribute = "Blah"};

Mock.ExpectCall(Repository.Save(domainObject)).Returns(True);
Mock.ReplayAll();

var sut = new Presenter(repository, view);
Save_Event.raise(view, EventArgs.Empty);

Mock.Verify()

So the problem here is that the domain object identity is calculated with ID and failing that it's calculated with transientID, there's no way to know what the transientID will be so I can't have the mock repository check for equality.

The workarounds so far are:

1) LastCall.Ignore and content myself with jsut testing that the method got called but not test the content of the call.

2) Write a DTO to test against and save to a service. The service than handles the mapping to domain.

3) Write a fake testRepository that uses custom logic to determine success.

--1 doesn't test the majority of the logic. --2 is a lot of extra code for no good purpose --3 Seems potentially brittle.

Right now I'm leaning towards DTO's and a service in the theory that it gives the greatest isolation between tiers but is probably 75% unnecessary...


Solution

  • there's no way to know what the transientID will be so I can't have the mock repository check for equality.

    Actually, I think there is an opportunity here.

    Instead of calling Guid.NewGuid(), you could create your own GuidFactory class that generates GUIDs. By default, it would use Guid.NewGuid() internally, but you could take control of it for tests.

    public static class GuidFactory
    {
        static Func<Guid> _strategy = () => Guid.NewGuid();
    
        public static Guid Build()
        {
            return _strategy();
        }
    
        public static void SetStrategy(Func<Guid> strategy)
        {
            _strategy = strategy;
        }
    }
    

    In your constructor, you replace Guid.NewGuid() with GuidFactory.Build().

    In your test setup, you override the strategy to suit your needs -- return a known Guid that you can use elsewhere in your tests or just output the default result to a field.

    For example:

    public class PseudoTest
    {
        IList<Guid> GeneratedGuids = new List<Guid>();
    
        public void SetUpTest()
        {
            GuidFactory.SetStrategy(() => 
            {
                var result = Guid.NewGuid();
                GeneratedGuids.Add(result);
                return result;
            });
        }
    
        public void Test()
        {
            systemUnderTest.DoSomething();
            Assert.AreEqual(GeneratedGuids.Last(), someOtherGuid);
        }
    }