Ayende @ Rahien

Refunds available at head office

Answer: How many tests?

Two days ago I asked how many tests this method need:

///<summary> 
///Get the latest published webcast 
///</summary>
public Webcast GetLatest();

Here is what I came up with:

[TestFixture]
public class WebcastRepositoryTest : DatabaseTestFixtureBase
{
	private IWebcastRepository webcastRepository;

	[TestFixtureSetUp]
	public void TestFixtureSetup()
	{
		IntializeNHibernateAndIoC(PersistenceFramework.ActiveRecord, 
			"windsor.boo", MappingInfo.FromAssemblyContaining<Webcast>());
	}

	[SetUp]
	public void Setup()
	{
		CurrentContext.CreateUnitOfWork();
		webcastRepository = IoC.Resolve<IWebcastRepository>();
	}

	[TearDown]
	public void Teardown()
	{
		CurrentContext.DisposeUnitOfWork();
	}

	[Test]
	public void Can_save_webcast()
	{
		var webcast = new Webcast { Name = "test", PublishDate = null };
		With.Transaction(() => webcastRepository.Save(webcast));
		Assert.AreNotEqual(0, webcast.Id);
	}

	[Test]
	public void Can_load_webcast()
	{
		var webcast = new Webcast { Name = "test", PublishDate = null };
		With.Transaction(() => webcastRepository.Save(webcast));
		UnitOfWork.CurrentSession.Evict(webcast);

		var webcast2 = webcastRepository.Get(webcast.Id);
		Assert.AreEqual(webcast.Id, webcast2.Id);
		Assert.AreEqual("test", webcast2.Name);
		Assert.IsNull(webcast2.PublishDate);
	}

	[Test]
	public void When_asking_for_latest_webcast_will_not_consider_any_that_is_not_published()
	{
		var webcast = new Webcast { Name = "test", PublishDate = null };
		With.Transaction(() => webcastRepository.Save(webcast));

		Assert.IsNull(webcastRepository.GetLatest());
	}

	[Test]
	public void When_asking_for_latest_webcast_will_get_published_webcast()
	{
		var webcast = new Webcast { Name = "test", PublishDate = null };
		With.Transaction(() => webcastRepository.Save(webcast));
		var webcast2 = new Webcast { Name = "test", PublishDate = DateTime.Now.AddDays(-1) };
		With.Transaction(() => webcastRepository.Save(webcast2));

		Assert.AreEqual(webcast2.Id, webcastRepository.GetLatest().Id);
	}

	[Test]
	public void When_asking_for_latest_webcast_will_get_the_latest_webcast()
	{
		var webcast = new Webcast { Name = "test", PublishDate = DateTime.Now.AddDays(-2) };
		With.Transaction(() => webcastRepository.Save(webcast));
		var webcast2 = new Webcast { Name = "test", PublishDate = DateTime.Now.AddDays(-1) };
		With.Transaction(() => webcastRepository.Save(webcast2));

		Assert.AreEqual(webcast2.Id, webcastRepository.GetLatest().Id);
	}

	[Test]
	public void When_asking_for_latest_webcast_will_not_consider_webcasts_published_in_the_future()
	{
		var webcast = new Webcast { Name = "test", PublishDate = DateTime.Now.AddDays(-2) };
		With.Transaction(() => webcastRepository.Save(webcast));
		var webcast2 = new Webcast { Name = "test", PublishDate = DateTime.Now.AddDays(2) };
		With.Transaction(() => webcastRepository.Save(webcast2));
		Assert.AreEqual(webcast.Id, webcastRepository.GetLatest().Id);
	}
}

And the implementation:

public class WebcastRepository : RepositoryDecorator<Webcast>, IWebcastRepository
{
	public WebcastRepository(IRepository<Webcast> repository)
	{
		Inner = repository;
	}

	public Webcast GetLatest()
	{
		var publishedWebcastsByDateDesc =
			from webcast in Webcasts
			where webcast.PublishDate != null && webcast.PublishDate < SystemTime.Now()
			orderby webcast.PublishDate descending 
			select webcast;

		return publishedWebcastsByDateDesc.FirstOrDefault();
	}

	private static IOrderedQueryable<Webcast> Webcasts
	{
		get { return UnitOfWork.CurrentSession.Linq<Webcast>(); }
	}
}

I think it is pretty sweet.

Comments

Vijay Santhanam
06/12/2008 10:46 AM by
Vijay Santhanam

I said 7, I was the closest I think.

Richard Lennox
06/12/2008 12:26 PM by
Richard Lennox

I am interested in why you haven't tested the default when there are no Webcasts in the repository? I can see that your GetLatesMethod has a FirstOrDefault() does the method for this test cover this scenario?

Ayende Rahien
06/12/2008 01:09 PM by
Ayende Rahien

First or default would return null, yes.

I actually forgot about this

Ayende Rahien
06/12/2008 01:31 PM by
Ayende Rahien

Hm, thinking about it, the test that test for no published results is the same, the code doesn't know if there are unpublished webcasts, after all

Bill Pierce
06/12/2008 02:57 PM by
Bill Pierce

Don't know that it should appear in your tests but the only other concern would be issues related to not using UTC dates when working with 'Latest' data.

Ayende Rahien
06/12/2008 03:04 PM by
Ayende Rahien

That is why I have SystemTime.Now()

Yeroc
06/13/2008 02:04 PM by
Yeroc

Forgive my ignorance. I"ve following your blog after learning how nice Rhino Mocks was. Could explain this line of code? I'm unfamiliar with much of what's going on here?

With.Transaction(() => webcastRepository.Save(webcast));

I assume With is one of the Rhino.Common classes? What is "() => " doing? I've never seen that syntax in C#.

Thanks

Ayende Rahien
06/13/2008 03:27 PM by
Ayende Rahien

This is part of Rhino Commons, it is a way to run under a transaction.

the ()=> is a labmda, a C# 3.0 concept

andy
06/15/2008 09:02 AM by
andy

looks like you're using NHibernate.Linq. what is the state of the project and would you also use it by now in a real project? When there will be the first release?

Ayende Rahien
06/15/2008 11:29 AM by
Ayende Rahien

I am using it, it is working well enough for most common scenarios.

We are still missing some parts in the more complex queries, but we are working on that.

Alexander Gro&#223;
06/16/2008 02:01 PM by
Alexander Groß

How do you register WebcastRepository in your Windsor config?

Because the class implements two interfaces (IRepository and IWebcastRepository for the "special" methods) it actually implements two services.

Do you register two services for the class? (Not sure if this is possible, though.)

From the application/controller point of view, when do you use

a) Repository.SomeMethod()

b) IoC.Resolve(IWebcastRepository).SomeMethod()

c) IoC.Resolve(WebcastRepository).SomeMethod()

?

Thanks,

Alex

Ayende Rahien
06/16/2008 02:05 PM by
Ayende Rahien

Here is my full configuration file:

import Castle.MonoRail.Framework

import Castle.MonoRail.WindsorExtension

import Rhino.Commons.Facilities from Rhino.Commons.ActiveRecord

import file from Configuration.boo

facility MonoRailFacility

facility RhinoTransactionFacility

facility ActiveRecordUnitOfWorkFacility:

assembly = "HibernatingRhinos"

for type in AllTypesBased of IController("HibernatingRhinos"):

component type.Name, type

for type in AllTypesBased of ViewComponent("HibernatingRhinos"):

component type.Name, type

fromHibernatingRhinosServices = {t as System.Type | t.Namespace == "HibernatingRhinos.Services"}

for type in AllTypes("HibernatingRhinos") \

.Where(fromHibernatingRhinosServices):

component type.GetFirstInterface(fromHibernatingRhinosServices), type

SetupConfigurationProperties()

Alexander Gro&#223;
06/16/2008 03:25 PM by
Alexander Groß

My question stems from the following use case: Say you're overriding methods of RepositoryDecorator in a derived class to have further validation happening before a save/insert takes place.

I would want users of the repository make "fall into the pit of success" (via Scott Hanselman) such that calls to Repository.Save are forced through the custom repo. The problem I see is that you now have

a) a custom repo that should be invoked when Repository is used

b) an extended repo with methods like GetLatest()

both implemented by one class, but accessible through two Windsor service interfaces.

From an application point of view you could now use one of the three APIs above (a, b, c). Which one do you prefer?

Alex

Ayende Rahien
06/16/2008 04:18 PM by
Ayende Rahien

In this case, I don't have different impl, so I don't care.

If I would, I would register WebcastRepository twice, once for IWebcastRepository and the second for IRepository

Alexander Gro&#195;Ÿ
06/16/2008 04:32 PM by
Alexander Groß

Sounds reasonable. That's also what I did, although it makes the registration a bit more complicated.

Denis
06/19/2008 09:46 PM by
Denis

It seems that the size of the test class compared to the size of the implementation class is going to frighten people. See http://www.dotnetguru.org/modules.php?op=modload&name=News&file=article&sid=1090 (in french).

I think you could underline that some of the code in the test class is not related to unitary testing GetLatest(). Many lines setup and test the persistance layer (cansave, canload). Shouldn't we assume that the persistance layer (the Inner property) is tested elsewhere by another set of unit tests ?

Comments have been closed on this topic.