How do I manipulate time in an ASP. NET Core test?

Source: Internet
Author: User
Tags assert datetime mscorlib static class

Sometimes, we encounter some requirements related to the current time of the system, such as:

    • Only the opening season is allowed to enter student information
    • It's only in the evenings or Saturday to allow backup blogs.
    • Users who have registered for 3 days are allowed to do something
    • A user is banned from speaking within 24 hours

It is clear that the code to implement these functions is more or less a DateTime.Now static attribute, but to use unit testing or integration testing to verify the above requirements, often need to adopt some curve saving method or even skip these tests directly, because in. Net, it is DateTime.Now often difficult to Mock. That's when I'm going to boast a Angular test tool that provides a perfect Mock-up of Date objects, so it's easy to manipulate the "current time" when writing test code.

After a few online searches, I found that the. Net FrameWork used to have such tools, not just mocks DateTime.Now , but many other mscorlib.dll methods and properties that could be mock. Such tools are broadly divided into three categories based on how they work, the first of which is to provide a mscorlib.dll way to generate false, and then add the generated fake DLL to the test project, the second class is to create a separate at run time AppDomain , and then in this AppDomain Temporarily generates an in-memory pseudo-assembly when the assembly is loaded, and another is to modify the reference address of the target function/property directly at run time. Of these three solutions, I am personally more inclined to the second-more flexible and will not change the existing process. However, the results of these searches are basically .Net Framework development-oriented, support for. Net Core and free of charge tools that I haven't found yet. Now I'm looking at smocks this project and trying to move him to. NET core, as a result of the lack of necessary APIs in Netstandard and the progress of Microsoft's development, they estimate that it will be up to. NET Core 3.0 to fill these APIs, this project can wait, But the project I have on hand can not afford, no way, only a botched replacement DateTime.Now to achieve similar functions.

With what to replace DateTime.Now

A qualified DateTime.Now alternative meets the following requirements:

    1. Because test cases tend to be multithreaded parallel random execution, the alternatives need to be isolated from each other in the thread
    2. In integration testing, the ASP and the test code are not running in the same thread, when the alternatives need to be able to be shared in the threads
    3. Ability to set the current time at any time
    4. In a production environment, it must be DateTime.Now consistent with functionality
    5. The signature of substitutes should be DateTime.Now consistent with

On the basis of this answer on the Web, I have myself transformed a class that is available in the ASP. NET Core Integration Test SystemClock :

&LT;SUMMARY&GT;///provides access to system time and allowing it to BES set to a fixed <see cref= "DateTime"/> value.///</summary>///<remarks>///This class is thread safe.///</remarks>public static class System    clock{private static readonly func<datetime> Default = () = DateTime.Now;    public static threadlocal<string> Clockid = new Threadlocal<string> (() = "prod"); public static dictionary<string, func<datetime>> Clocksmap = new dictionary<string, func<datetime    >> () {["prod"] = Default}; private static DateTime GetTime () {var fn = Clocksmap[clockid.value]??        Default;    return FN (); }///<inheritdoc cref= "Datetime.today"/> public static DateTime Today = GetTime ().    Date;    <inheritdoc cref= "DateTime.Now"/> public static DateTime now = GetTime (); <inheritdoc cref= "Datetime.utcnow"/> public static DateTime UtcNow =&Gt GetTime ().    ToUniversalTime (); <summary>//sets a fixed (deterministic) time for the current thread to return by <see cref= "DateTime"/&    gt;. </summary> public static void Set (DateTime time) {if (time. Kind! = datetimekind.local) time = time.        ToLocalTime ();    Clocksmap[clockid.value] = () = time;    }///<summary>//Initialize clock with a ID, so-you can share the clock across threads.        </summary>//<param name= "Clockid" ></param> public static void Init (string clockid) {        Clockid.value = Clockid;        if (Clocksmap.containskey (clockid) = = False) {Clocksmap[clockid] = Default; }}///<summary>//Resets <see cref= "Systemclock"/> To return the current <see cref= "DATETIME.N    ow "/&GT;    </summary> public static void Reset () {Clocksmap[clockid.value] = Default; }}

In the product code, you need to replace all of them manually DateTime.Now SystemClock.Now .

In the test code, a manual call is required SystemClock.Init(clockId) to initialize, which stores the incoming clockId storage as a static variable in the current thread, and sets a separate delegate for the Id to return DateTime . When used SystemClock.Now , it looks for the corresponding delegate in the current thread ClockId and returns the execution result. Thus, as long as multiple threads SystemClock.Init are in the same clockId call, we can share or set the return result in either of these threads, SystemClock.Now and the different threads, if clockid, are not affected by SystemClock.Now each other.

For example, let's say that there is one, in order to allow us to modify the execution result in the thread TestStartup.cs of execution of the test case code Controller , we SystemClock.Now first need to set the Configure method:

public override void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory){    // TestStartup.Configure 会在测试线程中调用    var clockId = Guid.NewGuid().ToString();    SystemClock.Init(clockId);    app.Use(async (context, next) =>    {        // 中间件的执行线程与测试线程不同但与 Controller、Service 的执行线程相同        SystemClock.Init(clockId);        await next();    });}

Since the thread that we requested might not be the same each time, I added the initialization code to the first middleware SystemClock . In the test case, we can manipulate the time:

public async void SomeTest(){    var now = new DateTime(2022,1,1);    SystemClock.Set(now);    // 注册用户    // Assert: 用户还不可以发言    var threeDaysAfter = now.AddDays(3);    SystemClock.Set(threeDaysAfter);    // Assert: 用户可以发言了
An idea

Because manual replacement DateTime.Now has a large change to existing code, the above is just a simple temporary workaround. But to solve this problem is not very difficult, you can try to dotnet build After the generated all the DLL through the tool to process, in the results of the compilation of the replacement of DateTime.Now , But recently there is not so much time, so remember here first (Dig pit reservation).

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.