# Tuesday, March 20, 2007

Well first off... where has this month gone??... it's been 2 weeks since I last blogged... I thought it was only a week!

At any rate, one of the things I've been working on lately is mocking out COM interfaces (using RhinoMocks of course) ... and I have to admit that my eyes start crossing after a while, at about the point where the mock repositories setup code begins dominating the test case... which happens all too often... here's a smaller example of what I'm talking about:

MockRepository repository = new MockRepository();

TaggedValue oneTaggedValue = repository.DynamicMock<TaggedValue>();
Expect.Call(oneTaggedValue.Value = "").Constraints(Is.Equal("ExpectedValue"));
Expect.Call(textAlignTaggedValue.Update()).Return(true);

TaggedValue anotherTaggedValue = repository.DynamicMock<TaggedValue>();
Expect.Call(anotherTaggedValue.Value = "").Constraints(Is.Equal("AnotherExpectedValue"));
Expect.Call(textAlignTaggedValue.Update()).Return(true);

Collection taggedValuesCollection = repository.DynamicMock<Collection>();

Expect.Call(taggedValuesCollection.GetByName("")).Constraints(Is.Equal("OneTaggedValue"))
    .Return(oneTaggedValue).Repeat.Any();

Expect.Call(taggedValuesCollection.GetByName("")).Constraints(Is.Equal("AnotherTaggedValue"))
    .Return(anotherTaggedValue).Repeat.Any();

Element destinationElement = repository.DynamicMock<Element>();
Expect.Call(destinationElement.TaggedValues).Return(taggedValuesCollection).Repeat.Any();

repository.ReplayAll();

So in this case we have an "Element", which has a collection of "TaggedValues", and we have two tagged values "OneTaggedValue" and "AnotherTaggedValue", and for each we are updating their values, and then invoking "Update()" on them to flush the changes...

To help solidify that, here's an example of what we're expecting to happen to our element (Element, Collection and TaggedValue are all interfaces from a COM interop assembly, in case you were wondering):

Element element;
...
TaggedValue oneTaggedValue = (TaggedValue)element.TaggedValues.GetByName("OneTaggedValue");
oneTaggedValue.Value = "ExpectedValue";
oneTaggedValue.Update(); TaggedValue anotherTaggedValue = (TaggedValue)element.TaggedValues.GetByName("AnotherTaggedValue"); anotherTaggedValue.Value = "AnotherExpectedValue";
anotherTaggedValue.Update();

Now normally in this case I take unit tests once I've got "green" and refactor the mocking code out a bit... so in the above case I'd probably end up with something like:

MockRepository repository = new MockRepository();
            
Collection taggedValuesCollection = repository.DynamicMock<Collection>();
AddTaggedValueSetter(repository, taggedValuesCollection, "OneTaggedValue", "ExpectedValue");
AddTaggedValueSetter(repository, taggedValuesCollection, "AnotherTaggedValue", "AnotherExpectedValue");

Element destinationElement = repository.DynamicMock<Element>();            
Expect.Call(destinationElement.TaggedValues).Return(taggedValuesCollection).Repeat.Any();

repository.ReplayAll();

Which works... generally quite well... but lately I've been moving away from that approach and instead have been refactoring the setup code into a fluent interface... which I've found to be a lot easier to maintain and extend (and tends to be a little more re-usable across multiple test fixtures...)


So for example, this is what the setup code for the above mock element is now:

MockRepository repository = new MockRepository();

Element element =
    Mock
    .Element()
    .HasTaggedValue("OneTaggedValue").ExpectSetValue("ExpectedValue")
    .HasTaggedValue("AnotherTaggedValue").ExpectSetValue("AnotherExpectedValue")
    .Complete(repository);

repository.ReplayAll();

I'm just skimming the surface of what I've been doing, but it might be food for thought for anyone working on similar projects... anyone else doing similar things?

posted @ Tuesday, March 20, 2007 10:23:38 PM (New Zealand Daylight Time, UTC+13:00)    Comments [0] | |
# Sunday, February 18, 2007
It seemed a long time coming, but finally Rhino Mock's 3.0 is available, which of course also means Dynamic Proxy 2 has made it to that sweet generic method support nirvana as well :)

I don't know where I'd be without these tools, I haven't done a .Net project without them in the last couple of years... in fact it's fueled my reluctance over the last couple of years for returning to bespoke development at any NZ dev shop which might dictate other (crappier) alternatives ;o)

I also noticed that mono is getting it's C# 3.0 support slowly sorted... Extension methods are in, as is some Lambda support... I need to sit down and spend some time thinking about how I should go about unit testing some of this stuff, it's on my list of "testing" things to do right after getting to grips with RhinoMocks 3 and perhaps writing another blog entry on mocking Base4 with all the new "goodies" - I need to do a monorail/base4 website for another little venture, so it's probably as good a time as any.

Went for a swim at Omaha today... just can't get enough of the beach this year - John Campbell turned up at the same time we did, there's only so many "Not in front of John" jokes I can handle in one day... or a life time ;o)
posted @ Sunday, February 18, 2007 7:11:28 PM (New Zealand Daylight Time, UTC+13:00)    Comments [0] | |
# Tuesday, February 13, 2007
Found myself writing a simple class for implanting "trial" logic for a client (as in you have 30 days left of your trial etc.) ... it relies on the current date and time, and so you can never really be assured your tests are robust unless you can control the current date & time within the test fixture (in fact any time you find yourself writing DateTime.Now alarm bells should be going off... even worse is logic based on DateTime.Now where it's evaluated more the once for a single function).

In the past I've generally Implemented something like an "IClock" interface, which would have a method like "DateTime GetCurrentTime()" or whatever seemed appropriate to the situation... but I decided to go with the lightweight solution of delegates instead in the morning, if only because I was being lazy I didn't see the need for creating an entire interface for such a trivial requirement... so we have:

private DateTime _trialExpirey;

private int _maxExecutions;

private TimeSpan _trialDuration;

private Func<DateTime> _nowFunction;

 

public TrialUpdater(DateTime trialExpirey, int maxExecutions, TimeSpan trialDuration)

    : this(delegate { return DateTime.Now; }, trialExpirey, maxExecutions, trialDuration)

{

}

 

public TrialUpdater(Func<DateTime> nowFunction, DateTime trialExpirey, int maxExecutions, TimeSpan trialDuration)

{

    if (nowFunction == null) throw new ArgumentNullException("nowFunction");

 

    _nowFunction = nowFunction;

    _trialExpirey = trialExpirey;

    _maxExecutions = maxExecutions;

    _trialDuration = trialDuration;           

}


But I'm starting to wonder if this code doesn't smell a bit if I start using it other places... for instance:
  • Could I supply an alternative delegate via the castle container...?
  • Could I mock out the nowFunction parameter easily using something like RhinoMocks and still have it participate in constraints...?
I'm thinking in the case of mocking it out you would probably need to implement some interfaces which lines up with the generic delegates (Func<Ret,T1,T2...> and Proc<T1,T2...> etc.) - so I would end mocking out an IFuncEvaluator<Ret> interface (or in this case IFuncEvaluator<DateTime>) -which would implement a method "T Evaluate();"... and the pass an anonymous delegate which in turn invokes the mocked IFuncEvaluator<Ret>.

Though maybe I've missed something and mocking out delegates is doable with the current RhinoMocks..?

As for being able to inject a value for the nowFunction parameter, I'm thinking a nice solution would be to actually wire it up to a method on another service declaratively i.e.

<component id="trialUpdater" type="MyLib.TrialUpdater, MyLib" >

  <parameters>

    <nowFunction>

      <method service="clockService" method="GetCurrentTime" />

    </nowFunction>

  </parameters>

</component>


Which I believe should be doable using a facility and a type converter... but I'm sure the devil's in the detail :)

At any rate, just a minor distraction, back to work.

As an aside the clocks a pretty boring example, but there are times when you want to manipulate the time for instance:
  • If you want to manipulate the time (return dates as UTC instead of Local)
  • Clamp the current Time so it's limited to a certain level of accuracy... perhaps you have an external expectation that all dates will be recorded at a 10ms level of accuracy (perhaps you want to align database timestamps with timestamps within your application).
  • Perhaps your application uses the clock to work with snapshots of your domain model in the past.
How many people are actually using anonymous delegates and generic Func<T...> / Proc<T...>'s in their day to day coding for tasks like this?

posted @ Tuesday, February 13, 2007 1:53:57 PM (New Zealand Daylight Time, UTC+13:00)    Comments [0] | |
# Tuesday, November 14, 2006

More mocking...

The title for this entry should probably be "more IRepository<T> mocking" - as that's what I'm mocking out... but at any rate, let's get down and dirty... In this entry I will cover mocking out a "complex" method, incidentally I think the size of this test suggests that our test isn't fine grained enough (and that individual features within the data loader should be exposed so we can test them better) - but that's my problem, not yours ;o)

Background

So, as some background, this is a test for a class called "DataLoader" - a most unimaginative name for a class that loads data... it's used for loading an XML file which has a bunch of info for different entities... if your recall the last entry, I was talking about simplistic music information, well this class can be used to load musical data into the store, including something not mentioned so far, which is the "test suite" - the test suite defines tests which can be run against the music in the store, the details aren't really up for discussion, but it's for executing customer acceptance tests, not unit tests.

The test below checks one of the primary paths, where by no pre-existing data is available, so it must all be created by the data loader first, before creating the customer acceptance test, which references the track, release etc. data (the definition for customer acceptance test suites are also stored in the store, as are the results from run the customer acceptance tests).

Diving In...

So here is the test for loading a "test suite" for a single track, the song "Dirty Harry" by the "Gorillaz", we are testing the behavior of loading the file when the track, release and artist do not already exist in the store - most of this is basic RhinoMock's usage (though I'm no expert, I'm probably misusing some of the features...).

[Test]

public void LoadDataFreshForSingleTrack()

{

    /**** RECORD ****/

 

    Environment environment = new Environment();

    SourceDevice sourceDevice = new SourceDevice();

    RecordingDevice recordingDevice = new RecordingDevice();

    Track track = new Track();

 

    ObjectPath artistPath = (Artist.Fields.Name == "Gorillaz");

    Expect.Call(_artistRepository.FindOne(artistPath)).Constraints(Base4Query.PathEqual(artistPath)).Return(null);

 

    _artistRepository.Save(null);

    LastCall.Constraints(Property.Value("Name", "Gorillaz"));

 

    ObjectPath releasePath = (Release.Fields.Name == "Demon Days");

    Expect.Call(_releaseRepository.FindOne(releasePath)).Constraints(Base4Query.PathEqual(releasePath)).Return(null);

 

    _releaseRepository.Save(null);

    LastCall.Constraints(Property.Value("Name", "Demon Days") &&

                        Property.ValueConstraint("Artist", Property.Value("Name", "Gorillaz")));

 

    _trackRepository.Save(null);

    LastCall.Constraints(Property.Value("Name", "Dirty Harry") &&

                        Property.ValueConstraint("Release", Property.Value("Name", "Demon Days")));

 

    ObjectPath encodingPath = (TrackContentEncoding.Fields.Name == "MP3");

    Expect.Call(_encodingRepository.FindOne(encodingPath)).Constraints(Base4Query.PathEqual(encodingPath)).

        Return(null);

 

    _trackContentRepository.Save(null);

    LastCall.Constraints(Property.ValueConstraint("Track", Property.Value("Name", "Dirty Harry")));                      

 

    ObjectPath trackReferencePath = (Track.Fields.Name == "Dirty Harry" &&

                                    Track.Fields.Release.Name == "Demon Days" &&

                                    Track.Fields.Release.Artist.Name == "Gorillaz");

 

    Expect.Call(_trackRepository.FindOne(trackReferencePath)).Constraints(

        Base4Query.PathEqual(trackReferencePath)).Return(track);

 

    Expect.Call(_trackRepository.FindOne(trackReferencePath)).Constraints(

        Base4Query.PathEqual(trackReferencePath)).Return(track);

 

    ObjectPath environmentPath = (Environment.Fields.Name == "Indoors");

    Expect.Call(_environmentRepository.FindOne(environmentPath)).Constraints(

        Base4Query.PathEqual(environmentPath)).Return(environment);

 

    ObjectPath sourceDevicePath = (SourceDevice.Fields.Name == "Loopback");

    Expect.Call(_sourceDeviceRepository.FindOne(sourceDevicePath)).Constraints(

        Base4Query.PathEqual(sourceDevicePath)).Return(sourceDevice);

 

    ObjectPath recordingDevicePath = (SourceDevice.Fields.Name == "Loopback");

    Expect.Call(_recordingDeviceRepository.FindOne(recordingDevicePath)).Constraints(

        Base4Query.PathEqual(recordingDevicePath)).Return(recordingDevice);

 

    _testSuiteRepository.Save(null);

    LastCall.Constraints(Property.Value("Name", "One track, one loopback sample") &&

                        Property.Value("Description",

                                        "Testing with 30 second snippets pulled from the source files with audacity (Loopback)") &&

                        Property.ValueConstraint("Tracks",

                            ItemListOf<Track>.NumberOfItems(1) && ItemListOf<Track>.IndexedItemConstraint(0, Is.Same(track))));

 

    /***** REPLAY *****/

 

    _mockRepository.ReplayAll();

 

    DataLoader loader = CreateLoader();

 

    loader.LoadData(OneTrackFile, ContentPath);

 

    _mockRepository.VerifyAll();  

}   

First you'll probably see that the callback and anonymous delegates from the last post are absent, they've been replaced with constraints... which has made the syntax a lot more concise - so far the rhino mocks support is pretty basic, we have two static classes being used for creating the constraints:

  • Base4Query - for constraints on ObjectQuery and ObjectPath arguments/properties.
  • ItemListOf<T> - for constraints on IItemList<T> arguments/properties.

The constraints method in RhinoMocks expects the same number of arguments as the associated method call on the mock object, constraints work on the arguments value, however you can apply constraints to the value of single property of an argument, and those constraints can be logically AND or OR'd together - for instance here:

_testSuiteRepository.Save(null);

LastCall.Constraints(Property.Value("Name", "One track, one loopback sample") &&

                    Property.Value("Description",

                                    "Testing with 30 second snippets pulled from the source files with audacity (Loopback)") &&

                    Property.ValueConstraint("Tracks",

                        ItemListOf<Track>.NumberOfItems(1) && ItemListOf<Track>.IndexedItemConstraint(0, Is.Same(track))));

We are applying these constraints:

  • The first argument should:
    • Have a property called "Name", and it's value should be "One track, one loopback sample".
    • And have a property called "Description", and it's value should be "Testing...." etc.
    • And have a property called "Tracks", which is of type IItemList<Track> which:
      • Contains a total of 1 items
      • And the item at index 0 should:
        • Be the same as the instance returned from an earlier save call.

Notice that we have to call the method we want to apply constraints to first, so that we can address it with LastCall - a necessary evil when the method has a return type of void... otherwise you can use the more pleasant "Expect.Call(...)" convention.

Also, I'm declaring instances of the expected Base4 object paths, and then applying them in the constraints... I could in-line the object paths, but I then need to disambiguate the overloaded call to the FindOne method, so we have my preference:

ObjectPath trackReferencePath = (Track.Fields.Name == "Dirty Harry" &&

                                Track.Fields.Release.Name == "Demon Days" &&

                                Track.Fields.Release.Artist.Name == "Gorillaz");

 

Expect.Call(_trackRepository.FindOne(trackReferencePath)).Constraints(

    Base4Query.PathEqual(trackReferencePath)).Return(track);

or this:

Expect.Call(_trackRepository.FindOne((ObjectPath)null)).Constraints(

    Base4Query.PathEqual(Track.Fields.Name == "Dirty Harry" &&

                    Track.Fields.Release.Name == "Demon Days" &&

                    Track.Fields.Release.Artist.Name == "Gorillaz")).Return(track);

The second involves less code, but I think it's easy to get confused between the compile time query language's logical operators and those of the RhinoMock's constraints... each have merits of course.

Conclusion...

I'm still working through some of the finer points of the implementation (and so won't publish any code just yet...) but this should give you some ideas about how you can test base4 interactions in your application with RhinoMocks, instead of hitting the base4 server and testing the results... and I think it's a lot more intuitive when attempting to do TDD with base4...

I would also like to make a special mention that this code won't actually work with the latest published release of Base4... there is an issue preventing the addition of items to an IItemList<T> in some situations when you have not set a valid default context - there is a fix (Which works well) in the Base4 trunk, and I assume Alex James will release this at some point... and thanks for resolving that issue so quickly Alex :) much appreciated.

posted @ Tuesday, November 14, 2006 3:18:15 PM (New Zealand Daylight Time, UTC+13:00)    Comments [0] | |
# Saturday, November 11, 2006

Domain Driven Design Afterthoughts...

I just finished reading Jimmy Nilsson’s Applying Domain-Driven Design and Patterns [ADDDP] book... I actually read it cover to cover, something I’ve found difficult to compel myself to do with some of the other books that have been lying around my desk for a wee while now (such as Petzold’s “Applications = Code + Markup” – a great book to assault someone with in a dark alley)...

First off, I think this book is a pretty good, it didn’t cover a lot of new ground for me, but It’s very down to earth, which I liked, and it’s encouraged me to have a read of Evan’s and Fowler’s more definitive works on the subject too... I also liked the fact that this book does attempt to tie the whole story together, from rough sketch through to identifying the domains language, using TDD to build up your domain model and even some of the gritty integration work, including evaluating OR/M features, using NHibernate as an example, and even looks into inversion of control contains (spring sadly, it would’ve been great to have seen Castle get a mention) and finally AOP. On the down side – I think the book could’ve tackled the application of rules to your domain model a little better (I wanted to see more code) and depending on your TDD knowledge, you might find that a couple of the chapters don’t really do much for you as there’s little focus on the model so much as the key concepts of red, green, refactor...

At any rate, one thing I did keep rolling around in the back of my mind is just how Base4.Net fits into the “domain model” picture ... it’s difficult to nail down, there are plenty of mechanisms for implementing most of what you need to create a domain model, for instance:

  • Inheritance, though it’s support for discriminators in user types isn’t quite up to scratch – it only works via “ItemBase” at the moment – though I think Alex James mentioned that this would be implemented at some point... and though I haven’t tried, you could roll your own in some way.
  • Aggregates (through extended properties).
  • Various hooks (Events) to allow for the application of custom behavior, for instance you could wire up to a BeforeSave event on a type and implement some custom validation rules... not that easy to test though .
  • A reasonable query abstraction - Good query support.
  • A “logical” transaction mechanism, suitable for supporting the concept of a “UnitOfWork”, though it’s explicit, rather the implicit, and based on the examples in the documentation this could be a little annoying to work with when you’re trying to persist the entire graph for an aggregate – however using a similar implementation for “UnitOfWork” as Ayende does in the Rhino.Commons library for NHibernate I’m sure I could get it all working nicely, without too much trouble.

And it sounds like I’m on to a winner... but I think what I struggle with is that the types in your schema are not really the focal point for your domain model, because they’re not POCO, unlike say a domain model implemented with NHibernate as the backing O/RM can be, you can’t enrich or decorate them with additional functionality all that easily... It’s not that you have to build your domain model this way, it’s just that’s the way I would like to do it, at least to satisfy me that I'm not being railroaded into a bad design choice – but I think it’s the small blood price you pay for letting Base4 generate the schema assemblies for you, that loss of control is also a boon in immediate productivity when you start developing apps with Base4 apps... I’ve come close while using ActiveRecord, but it’s still not the same.

So... given the restrictions I’m left to implementing additional abstractions for my domain model... which means using repositories and services for encapsulating the business logic... which, in turn, brings me to mocking...

Mocking out my Base4 Implementation...

Now, if you recall a while back I talked about my repository implementation... basically it let you do things like:

IRepository<Order> orderRepository = IoC.Resolve<IRepository<Order>>();                       

 

Order orderForApples = new Order();

OrderLine greenOnes = new OrderLine();

OrderLine redOnes = new OrderLine();

orderForApples.OrderLines.Add(greenOnes);

orderForApples.OrderLines.Add(redOnes);

 

orderRepository.Save(orderForApples);

Big woop, but what I probably neglected to mention is that the repository is great for implementing a chain of generic decorators (which can be set up in your IoC container of course)... so at the bottom/base of the chain we may have our “Base4StorageRepository” and layered on top of that we might have various decorators (each injected with a dependency for the next repository in the chain) for implementing some useful concepts...

Things that spring to mind are:

  • Security
  • Validation
  • Logging

Being able to configure these things is quite useful, and there’s little stopping you deploying additional decorators as additional assemblies for an already installed product – just throw in some additional container configuration - and it is a great deal more elegant then implementing this functionality with AOP.

But you do kind of paint yourself in a corner at the same time... this generic decorator pattern stops you from being able to decorate the repository with additional methods for implementing business level functionality (because any decorations that are applied to your base repositorty will mask out  methods and properties no present in the IRepository<T> interface)... that’s fine though, I guess we just have to write the repository off as being more of a persistence mechanism, It’s really a logical separation of concerns anyway... you can decorate a "wrapper" at the top of the chain though (and this is how Ayende does, but lets ignore that for now ;o)

So... A higher level entity for dealing with the business level concerns is required... I call mine services, that may or may not sit right with you, but it makes reasonable sense in my application – and these service are injected as dependencies of the controllers re: MVC, yeah this is a Monorail app (or at least, part of it is)...

These services often aggregate the features of multiple repositories, like this catalogue service below which deals with a simple music structure:

public class CatalogueService : ICatalogueService

{

    public CatalogueService(IRepository<Track> trackRepository, IRepository<Release> releaseRepository,

                            IRepository<Artist> artistRepository, IRepository<Genre> genreRepository)

    {

        if (trackRepository == null) throw new ArgumentNullException("trackRepository");

        if (releaseRepository == null) throw new ArgumentNullException("releaseRepository");

        if (artistRepository == null) throw new ArgumentNullException("artistRepository");

        if (genreRepository == null) throw new ArgumentNullException("genreRepository");

 

        _trackRepository = trackRepository;

        _releaseRepository = releaseRepository;

        _artistRepository = artistRepository;

        _genreRepository = genreRepository;

    }

In this case we have a dependency on four different repositories...

For this example we have a pretty simple schema... with a child-parent relationship between Track, Release and Artist... and a Many to Many relationship between Tracks and Genres...

Track -> Release -> Artist
Track(s) <-> Genre(s)

The catalogue service implements the business rules for dealing with the catalogue, in some cases this is no more than querying the associated repository... so for getting a list of releases for a particular artist we have this (and yeah, I know it’s pretty daft):

public PagedItemList<Track> ListTracksForArtist(Artist artist, int pageSize, int pageNumber)

{

    ObjectQuery query = new ObjectQuery(typeof (Track));

    query.Scope.Add("Release");

    query.Scope.Add("Release.Artist");

    query.Path = (Track.Fields.Release.Artist.ID == artist.ID);

    query.Path.AddOrderBy("Name", OrderByDirection.Ascending);

 

    return _trackRepository.Find(query, pageSize, pageNumber);

}

I say daft because it doesn’t support sorting and filtering by a query... but it’s here to illustrate a point, and until the customer actually asks for these features I’m not going to bother building them :P

At any rate, the point is not to critique the service, but instead how can we test this catalogue service without being connected to a Base4 server... and of course it’s RhinoMocks to the rescue!

Mocking with RhinoMocks...

So here’s the guts of the test in mid-refactoring ... post red-green for those sticklers for the rules ;o) (there’s still plenty yet to clean up, but it would muddy the waters a bit for this example I think...)

[Test]

public void ListReleasesForArtist()

{           

 

    Artist artist = new Artist();

 

    PagedItemList<Release> releases = new PagedItemList<Release>(new ItemList<Release>(), 1, 10, 20);

 

    Func<bool, ObjectQuery, int, int> callback

        = delegate(ObjectQuery query, int pageNumber, int pageSize)

        {

            ObjectPath path = Release.Fields.Artist.ID == artist.ID;

            path.AddOrderBy("Name");

 

            Base4Assert.ArePathsEqual(path, query.Path);

            Base4Assert.AreScopesEqual(new string[] { "Artist" }, query.Scope);

            Assert.AreEqual(1, pageNumber, "pageNumber");

            Assert.AreEqual(10, pageSize, "pageSize");

            return true;

        };

 

 

    Expect.Call(_releaseRepository.Find(null, 1, 10)).Callback(callback).Return(releases);

 

    _mockRepository.ReplayAll();

 

    ICatalogueService service =

        new CatalogueService(_trackRepository, _releaseRepository, _artistRepository, _genreRepository);

 

    PagedItemList<Release> results = service.ListReleasesForArtist(artist, 1, 10);

    Assert.AreSame(releases, results);

 

    _mockRepository.VerifyAll();

}

Pretty chunky I know - but as more tests are added there will be opportunities for removing some of that duplicated effort... however the key points to take away are:

  • We don’t need to have the base4 service running.
  • We are actually testing the catalogue service’s interactions with the repositories, instead of relying on detecting expected side effects in the underlying storage.
  • We’re verifying the object path and scope for the query, as well as paging information, and insulating ourselves from difficult to detect changes (like forgetting to apply an object scope, which may have a severe impact on performance).

Just to complete the story, the mock repository was created in the Setup (as we use it for every test case)... here's the code for it:

[SetUp]

public void SetUp()

{

    _mockRepository = new MockRepository();

 

    _trackRepository = _mockRepository.CreateMock<IRepository<Track>>();

    _releaseRepository = _mockRepository.CreateMock<IRepository<Release>>();

    _artistRepository = _mockRepository.CreateMock<IRepository<Artist>>();

    _genreRepository = _mockRepository.CreateMock<IRepository<Genre>>();

}

It doesn’t stop us from incorrectly spelling an object scope I think compile time query support for ordering and scoping of an object query will help to make this a little more robust... small potatoes.

The callback in this case is a bit of a “bad smell” – there’s support in rhino mocks for parameter constraints, but the object paths and scopes are a little too complex to test using the out the box ones... though you can get surprisingly close, they are pretty powerful... but I believe you can write custom constraints yourself – which is something I’ll do for the next post (I’ve never done it before, I'm guessing it's easy) and hopefully that will reduce the complexity of these tests quite a bit, and replace the less concise anonymous delegate, and more importantly, make it easy to develop the catalogue service in a test-driven manor.

For those more observant people you may have noticed the “Base4Assert” as well – that’s a little static helper class I’m using in these tests... it’s not perfect, but it works for simple cases including things like multi-level scopes and gives meaningful failure methods like “expected scope ‘Release.Artist’ but found nothing.” Or "expected OrderBy Name Descending, but found OrderBy Name Ascending"... which can quickly narrow down problems for you, especially if your writing these tests first (which is the whole point of this exercise I feel).

Conclusion... For Now ;o)

Last thing I’ve done is to have tossed away a lot of the additional query overloads in my original repository design in favor of just using a single ObjectQuery parameter with and without page number and page size (returning a PagedItemList<T> when providing a page number and page size, and returning an IItemList<T> when querying without them) – It makes everything a lot more... predictable, and is a lot cleaner when you start considering generic decorator implementations (10 overloads is 10 more code paths that need testing in a decorator... ) - looking towards to the future it should conceivable that a compile time query will allow you to construct the entire object query, not just the unordered path, and I’m quite happy to build these up in a few lines of code before passing them to a find method for now.

Edit (Sunday 12th):

In a similar vein, I just noticed Ayende's great MSDN article this evening - which covers that whole IRepository<T> story, including generic decorators and most importantly the intricacies of registering and chaining these components in the windsor container... great stuff!

posted @ Saturday, November 11, 2006 12:00:12 AM (New Zealand Daylight Time, UTC+13:00)    Comments [3] | |
Search
FeedCount

Tags...
.Net (83) .Net Reactor (4) .net user groups (9) 2008SummerRoadTrip (1) ActiveRecord (1) architecture (1) architecture chat (95) ArchitectureCamp2007 (2) asp.net (1) Astoria (1) Auckland (1) base4 (9) batching (1) binsor (1) blog (4) boo (1) books (1) C# 3.0 (9) cambodia (9) CAML.Net (1) castle (40) china (8) codecamp (3) codeplex (3) dapper.net (1) DevDefined Ltd. (4) DirectShow.Net (1) DLR (1) DSL (4) EAUG (1) Enterprise Architect (5) Enterprise Architecture (1) Enterprise Library (1) F# (1) feedburner (2) first post (1) Friendster (1) generics (1) googlegears (1) hacks (3) hardware (3) hongkong (2) Horn (1) hyper-v (1) ideas (1) IoC (21) IronPython (13) IronRuby (2) jobs (1) Languages (2) laos (8) LINQ (7) LiveId (1) LLU (1) Local Government (1) MDA (1) MDD (1) microsoft (1) Model Driven Development (1) mono (1) monorail (2) Movies (1) Music (1) nDepend (1) news (1) NHibernate (3) NUnit (2) nvelocity (1) OAuth (6) office (1) OpenSocial (1) orcon (1) photos (1) php (1) PostSharp (1) powerpoint (1) presentations (1) ReSharper (1) REST (2) rhino commons (3) rhinomocks (5) Ruby (1) SaaS (1) scm (1) Screen Architect (0) SharePoint (5) silverlight (1) Splicer (4) SQL2008 (1) supcom (1) survey (1) svn (1) Syzmk (4) thailand (6) Tools (2) Tortoise SVN (1) trac (2) Travel (36) Unity (2) vietnam (7) vista (2) visual nhibernate (1) vmware (1) volta (3) VS2008 (1) WCF (3) wiki (2) wikipedia (1) Windows Server 2008 (1) windsor (6) WinForms (1) wix (2) WPF (2) xmlrpc (1) yahoo pipes (1)
Who am I?
Alex Henderson
Alex Henderson
Auckland, New Zealand
Managing Director at Dev|Defined Limited

"Self Confessed Coding Junky for 15 years"
View Alex Henderson's profile on LinkedIn
 
Mobile: +64-21-402-969
Email: bittercoder 'at' gmail 'dot' com
MSN: bittercoder_nz@hotmail
Skype: alex.devdefined
Navigation