Is there an easy way to stub out time.Now() globally during test?

前端 未结 7 1683
小鲜肉
小鲜肉 2020-12-05 06:43

Part of our code is time sensitive and we need to able to reserve something and then release it in 30-60 seconds etc, which we can just do a time.Sleep(60 * time.Secon

相关标签:
7条回答
  • 2020-12-05 06:53

    Also if you need to just stub time.Now you can inject dependency as a function, e.g.

    func moonPhase(now func() time.Time) {
      if now == nil {
        now = time.Now
      }
    
      // use now()...
    }
    
    // Then dependent code uses just
    moonPhase(nil)
    
    // And tests inject own version
    stubNow := func() time.Time { return time.Unix(1515151515, 0) }
    moonPhase(stubNow)
    

    Granted all that is a bit ugly if you come from dynamic languages background (e.g. Ruby) :(

    0 讨论(0)
  • 2020-12-05 06:54

    From Google result I found relatively simple solution: Here

    The basic idea is using another function call "nowFunc" to get the time.Now(). In your main, initialize this function to return time.Now(). In your test, initialize this function to return a fixed fake time.

    0 讨论(0)
  • 2020-12-05 06:58

    I use the bouk/monkey package to replace the time.Now() calls in my code with a fake:

    package main
    
    import (
        "fmt"
        "time"
    
        "github.com/bouk/monkey"
    )
    
    func main() {
        wayback := time.Date(1974, time.May, 19, 1, 2, 3, 4, time.UTC)
        patch := monkey.Patch(time.Now, func() time.Time { return wayback })
        defer patch.Unpatch()
        fmt.Printf("It is now %s\n", time.Now())
    }
    

    This works well in tests to fake out system dependencies and avoids the abused DI pattern. Production code stays separate from test code and you gain useful control of system dependencies.

    0 讨论(0)
  • 2020-12-05 07:08

    There are multiple way to mock or stub time.Now() in test code:

    • Passing an instance of time to the function
    func CheckEndOfMonth(now time.Time) {
      ...
    }
    
    • Passing a generator to the function
    CheckEndOfMonth(now func() time.Time) {
        // ...
        x := now()
    }
    
    • Abstract with an interface
    type Clock interface {
        Now() time.Time
    }
    
    type realClock struct {}
    func (realClock) Now() time.Time { return time.Now() }
    
    func main() {
        CheckEndOfMonth(realClock{})
    }
    
    • Package level time generator function
    type nowFuncT func() time.Time
    
    var nowFunc nowFuncT
    
    func TestCheckEndOfMonth(t *Testing.T) {
        nowFunc = func() time.Time {
            return time.Now()
        }
        defer function() {
            nowFunc = time.Now
        }
    
        // Test your code here
    }
    
    • Embed time generator in struct
    type TimeValidator struct {
        // .. your fields
        clock func() time.Time
    }
    
    func (t TimeValidator) CheckEndOfMonth()  {
        x := t.now()
        // ...
    }
    
    func (t TimeValidator) now() time.Time  {
        if t.clock == nil {
            return time.Now() // default implementation which fall back to standard library
        }
    
        return t.clock()
    }
    

    Each has it's own plus and minus. Best way is to separate the function that generates the time and the processing part that uses the time.

    This Post Stubing Time in golang goes into details about it and there is an example for making function with time dependency to be easily tested.

    0 讨论(0)
  • 2020-12-05 07:10

    If the methods you need to mock are few, such as Now(), you can make a package variable which can be overwritten by tests:

    package foo
    
    import "time"
    
    var Now = time.Now
    
    // The rest of your code...which calls Now() instead of time.Now()
    

    then in your test file:

    package foo
    
    import (
        "testing"
        "time"
    )
    
    var Now = func() time.Time { return ... }
    
    // Your tests
    
    0 讨论(0)
  • 2020-12-05 07:12

    With implementing a custom interface you are already on the right way. I take it you use the following advise from the golang nuts thread you've posted:


    type Clock interface {
      Now() time.Time
      After(d time.Duration) <-chan time.Time
    }
    

    and provide a concrete implementation

    type realClock struct{}
    func (realClock) Now() time.Time { return time.Now() }
    func (realClock) After(d time.Duration) <-chan time.Time { return time.After(d) }
    

    and a testing implementation.


    Original

    Changing the system time while making tests (or in general) is a bad idea. You don't know what depends on the system time while executing tests and you don't want to find out the hard way by spending days of debugging into that. Just don't do it.

    There is also no way to shadow the time package globally and doing that would not do anything more you couldn't do with the interface solution. You can write your own time package which uses the standard library and provides a function to switch to a mock time library for testing if it is the time object you need to pass around with the interface solution that is bothering you.

    The best way to design and test your code would probably be to make as much code stateless as possible. Split your functionality in testable, stateless parts. Testing these components separately is much easier then. Also, less side effects means that it is much easier to make the code run concurrently.

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