0
votes

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...

2

2 Answers

1
votes

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);
    }
}
1
votes

WPF has helped me realize that you really don't need to do much testing if any on the Controller/Presenter/VM. You really should focus all of your tests on the models and services you use. All business logic should be there, the view model or presenter or controller should be as light as possible, and only role is to transfer back and forth between the model and the view.

What's the point of testing whether you call a service when a button command makes it to the presenter? Or testing whether an event is wired properly?

Don't get me wrong, I still have a very small test fixture for the view models or controllers but really the focus of tests should be on the models, let the integration tests test the success of the view and the presenter.

Skinny controllers/VMs/Presenters. Fat Models.

This is my answer because I ran into the same issue trying to test viewmodels, I wasted so much time trying to figure out how best to test them and another developer gave a great talk on Model-View patterns with this argument. Don't spend too much time making tests for these, focus on the models/services.