Possible to reset state of dispatch_once in unit test, to make them run again

前端 未结 2 1472
别跟我提以往
别跟我提以往 2020-12-08 07:33

Is it possible to reset the state of dispatch_once code in a unit test tearDown?

I think it would be nice if our unit tests could run from a really clean state, but

相关标签:
2条回答
  • 2020-12-08 07:59

    We too unit test our singletons and occasionally need to replace them with mock objects or reset them. I took Josh's answer and simplified it a bit further:

    static ArticleManager *_sharedInstance = nil;
    static dispatch_once_t once_token = 0;
    
    +(ArticleManager *)sharedInstance {
        dispatch_once(&once_token, ^{
            if (_sharedInstance == nil) {
                _sharedInstance = [[ArticleManager alloc] init];
            }
        });
        return _sharedInstance;
    }
    
    +(void)setSharedInstance:(ArticleManager *)instance {
        if (instance == nil) once_token = 0;
        _sharedInstance = instance;
    }
    
    0 讨论(0)
  • 2020-12-08 08:03

    I should note first that this isn't a good thing to do in any situation other than testing; even then, proceed with care -- AliSoftware provides some details and example code in comments below. See also the interesting answers at Can I declare a dispatch_once_t predicate as a member variable instead of static?, including some important information from the horse's mouth.

    dispatch_once_t is a typedefd long. Its false value is 0. If you reset that flag to 0, dispatch_once() will run again. Your problem is "just" how to change the value of a static variable from another compilation unit. For this, I think you need a debug/unit test hook, like so:

    MakeWhoopie.h

    #import <Foundation/Foundation.h>
    
    void makeWhoopie(void);
    
    #ifdef DEBUG
    void resetDispatchOnce(void);
    #endif
    

    MakeWhoopie.m

    #include "MakeWhoopie.h"
    
    static dispatch_once_t * once_token_debug;
    
    void makeWhoopie(void)
    {
    
        static dispatch_once_t once_token;
        once_token_debug = &once_token;    // Store address of once_token
                                           // to access it in debug function.
        dispatch_once(&once_token, ^{
            NSLog(@"That's what you get, folks.");
        });
    
        NSLog(@"Making whoopie.");
    }
    
    #ifdef DEBUG
    void resetDispatchOnce(void)
    {
        *once_token_debug = 0;
    }
    #endif
    

    (You could also move once_token up to file level and change it directly.)

    Trying this out:

    #import <Foundation/Foundation.h>
    #import "MakeWhoopie.h"
    
    int main(int argc, const char * argv[])
    {
    
        @autoreleasepool {
    
            makeWhoopie();
            makeWhoopie();
            resetDispatchOnce();
            makeWhoopie();
        }
        return 0;
    }
    

    Results in:

    2012-06-07 18:45:28.134 ResetDispatchOnce[8628:403] That's what you get, folks.
    2012-06-07 18:45:28.163 ResetDispatchOnce[8628:403] Making whoopie.
    2012-06-07 18:45:28.164 ResetDispatchOnce[8628:403] Making whoopie.
    2012-06-07 18:45:28.165 ResetDispatchOnce[8628:403] That's what you get, folks.
    2012-06-07 18:45:28.165 ResetDispatchOnce[8628:403] Making whoopie.

    0 讨论(0)
提交回复
热议问题