DATA-DOG / go-sqlmock

Sql mock driver for golang to test database interactions
Other
6.02k stars 406 forks source link

Mock time.Now() #262

Closed gmhafiz closed 2 months ago

gmhafiz commented 3 years ago

I have a function with

func (r *repository) Update(ctx context.Context, book *models.Book) error {
    now := time.Now()

    _, err := r.db.ExecContext(ctx, UPDATE books set title = $1, description = $2, published_date = $3, image_url = $4, updated_at = $5 where book_id = $6, book.Title, book.Description,
        book.PublishedDate, book.ImageURL, now, book.BookID)
    if err != nil {
        return err
    }

    return nil
}

that I tried to unit test with

func TestRepository_Update(t *testing.T) {
    db, mock := NewMock()
    repo := New(db)

    mockBook := &models.Book{
        BookID:        1,
        Title:         "test1",
        PublishedDate: timeWant(),
        ImageURL: null.String{
            String: "https://example.com/image.png",
            Valid:  true,
        },
        Description: "test1",
    }

    mock.ExpectExec("UPDATE books set title").
        WithArgs(mockBook.Title, mockBook.Description, mockBook.PublishedDate, mockBook.ImageURL.String, time.Now().String(), mockBook.BookID).
        WillReturnResult(sqlmock.NewErrorResult(nil))

    err := repo.Update(context.Background(), mockBook)

    assert.NoError(t, err)
}

The fails obviously because the value of time.Now() in the test and in the implementation are different.

=== RUN   TestRepository_Update
    postgres_test.go:189: 
            Error Trace:    postgres_test.go:189
            Error:          Received unexpected error:
                            ExecQuery 'UPDATE books set title = $1, description = $2, published_date = $3, image_url = $4, updated_at = $5 where book_id = $6', arguments do not match: argument 4 expected [string - 2021-05-26 22:07:58.847659183 +1000 AEST m=+0.001800825] does not match actual [time.Time - 2021-05-26 22:07:58.847710743 +1000 AEST m=+0.001852395]
            Test:           TestRepository_Update
--- FAIL: TestRepository_Update (0.00s)

How do you write unit a test when the implementation has a value that will change like time.Now() or somehow mock time.Now()

For reference, book struct

type Book struct {
    BookID        int64       `db:"book_id" json:"book_id"`
    Title         string      `db:"title" json:"title"`
    PublishedDate time.Time   `db:"published_date" json:"published_date"`
    ImageURL      null.String `db:"image_url" json:"image_url,`
    Description   string      `db:"description" json:"description"`
    CreatedAt     null.Time   `db:"created_at" json:"created_at,`
    UpdatedAt     null.Time   `db:"updated_at" json:"updated_at,`
    DeletedAt     null.Time   `db:"deleted_at" json:"deleted_at,`
}
l3pp4rd commented 3 years ago

Have you read the api docs of sqlmock before asking?

egregors commented 3 years ago

@gmhafiz check Customize SQL query matching chapter of https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock?utm_source=godoc

linnaname commented 2 years ago

https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock#readme-matching-arguments-like-time-time

ibadi-id commented 2 years ago

Add this into your test file

type AnyTime struct{}

func (a AnyTime) Match(v driver.Value) bool {
    _, ok := v.(time.Time)
    return ok
}

and replace your data time.now() with AnyTime{}

mock.ExpectExec("UPDATE books set title").
        WithArgs(mockBook.Title, mockBook.Description, mockBook.PublishedDate, mockBook.ImageURL.String, AnyTime{}, mockBook.BookID).
        WillReturnResult(sqlmock.NewErrorResult(nil))