Comment se moquer de DateTime.Maintenant dans les tests unitaires?

La solution normale est de se cacher derrière l'interface.

public class RecordService
{
    private readonly ISystemTime systemTime;

    public RecordService(ISystemTime systemTime)
    {
        this.systemTime = systemTime;
    }

    public void RouteRecord(Record record)
    {
        if (record.Created < 
            systemTime.CurrentTime().AddMonths(-2))
        {
            //process old record
        }

        //process the record
    }
}

Dans l'unité de test, vous pouvez utiliser l'objet fantaisie et de décider ce qu'il doit retourner

[TestClass]
public class When_old_record_is_processed
{
    [TestMethod]
    public void Then_it_is_moved_into_old_records_folder()
    {
        var systemTime = A.Fake<ISystemTime>();
        A.CallTo( () => system.Time.CurrentTime())
          .Returns(DateTime.Now.AddYears(-1));

        var record = new Record(DateTime.Now);
        var service = new RecordService(systemTime);

        service.RouteRecord(record);

        //Asserts...
    }
}

Je n'aime pas à injecter de l'interface dans ma classe juste pour obtenir l'heure actuelle. Il se sent trop lourds solution pour un petit problème. La solution est d'utiliser la classe statique avec la fonction publique.

public static class SystemTime
{
    public static Func<DateTime> Now = () => DateTime.Now;
}

Maintenant, nous pouvons retirer le ISystemTime injection et RecordService ressemble à ceci

public class RecordService
{
    public void RouteRecord(Record record)
    {
        if (record.Created < SystemTime.Now.AddMonths(-2))
        {
            //process old record
        }

    //process the record
    }
}

Dans les tests unitaires, on peut se moquer de l'heure système tout aussi facilement.

[TestClass]
public class When_old_record_is_processed
{
    [TestMethod]
    public void Then_it_is_moved_into_old_records_folder()
    { 
        SystemTime.Now = () => DateTime.Now.AddYears(-1);
        var record = new Record(DateTime.Now);
        var service = new RecordService();

        service.RouteRecord(record);

        //Asserts...
    }
}

Bien sûr, il ya un inconvénient à tout cela. Vous êtes à l'aide de champs publics (L'HORREUR!) donc, personne ne vous en empêche l'écriture de code comme ceci.

public class RecordService
{
    public void RouteRecord(Record record)
    { 
        SystemTime.Now = () => DateTime.Now.AddYears(10);
    } 
}

Aussi je pense que c'est mieux d'éduquer les développeurs de créer des abstractions juste de les protéger, de faire des erreurs. D'autres problèmes sont liés à l'exécution des tests. Si vous oubliez de restaurer la fonction de retour à son état d'origine, il pourrait affecter d'autres tests. Cela dépend de la façon dont l'unité de test runner exécute les tests.
Vous pouvez utiliser la même logique pour se moquer les opérations de système de fichiers

public static class FileSystem
{
    public static Action<string, string> MoveFile = File.Move;
}

À mon avis, la mise en œuvre de ce type de fonctionnalité (se moquant de temps, de simples opérations de système de fichiers) à l'aide de la fonction publique est parfaitement acceptable. Elle rend le code plus facile à lire, diminue les dépendances et il est facile de se moquer dans les tests unitaires.

Avez-vous utilisé moq? (ou tout autre moqueur cadre) code.google.com/p/moq
J'ai corrigé le formatage de votre code. Veuillez comparer votre version d'apprendre comment le faire correctement.
Comme tous les autres "normal" se moquant de cadres, Moq ne peut pas se moquer de méthodes et propriétés statiques comme DateTime.Now.
Je ne vois pas de question - c'est plus comme un blog-post
Grand. Cool beans. Quelle est la question?

OriginalL'auteur Unknown | 2012-03-30