Skip to content

Commit

Permalink
Moved the Documentation from Wiki to Code
Browse files Browse the repository at this point in the history
Moved the documentation for better collaboration
Added examples
Added gotchas I encountered
Fixed typos and sentence structure
  • Loading branch information
vallieres committed Oct 23, 2018
1 parent 3698001 commit e3c3b4f
Showing 1 changed file with 100 additions and 42 deletions.
142 changes: 100 additions & 42 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
### Setup tests
# go-mocket Documentation

## Setting Up Tests

***

To setup tests, you need register driver and override DB instance used across a codebase.
To set up tests, you need to register the driver and override the DB instance used across the code base.
```go
import (
"database/sql"
Expand All @@ -13,7 +15,7 @@ import (
func SetupTests() *sql.DB { // or *gorm.DB
mocket.Catcher.Register() // Safe register. Allowed multiple calls to save
// GORM
db, err := gorm.Open(mocket.DRIVER_NAME, "connection_string") // Could be any connection string
db, err := gorm.Open(mocket.DRIVER_NAME, "connection_string") // Can be any connection string
DB = db

// OR
Expand All @@ -24,9 +26,9 @@ func SetupTests() *sql.DB { // or *gorm.DB
}
```

In the snippet above intentionally skipped assigning to proper variable DB instance. One of the assumptions used that every project has one DB instance at the time, overriding it with FakeDriver will do its job.
In the snippet above, we intentionally skipped assigning to proper variable DB instance. One of the assumptions is that the project has one DB instance at the time, overriding it with FakeDriver will do the job.

### Simple chain usage
## Simple Chain Usage

***

Expand Down Expand Up @@ -61,9 +63,9 @@ func TestResponses(t *testing.T) {

t.Run("Simple SELECT caught by query", func(t *testing.T) {
Catcher.Logging = false
commonReply := []map[string]interface{}{{"name": "FirstLast", "age": "30"}}
commonReply := []map[string]interface{}{{"user_id": 27, "name": "FirstLast", "age": "30"}}
Catcher.Reset().NewMock().WithQuery(`SELECT name FROM users WHERE`).WithReply(commonReply)
result := GetUsers(DB) // GLobal or local variable
result := GetUsers(DB) // Global or local variable
if len(result) != 1 {
t.Errorf("Returned sets is not equal to 1. Received %d", len(result))
}
Expand All @@ -73,16 +75,17 @@ func TestResponses(t *testing.T) {
})
}
```
In example above we create New mock via `.NewMock()` and attach query pattern which will be used to catch query.`.WithReply()` specifies which response will be provided during mock of this request.
As `Catcher` is global variable without calling `.Reset()` this mock will be applied to all tests and queries if the pattern will be matched.
In the example above, we create a new mock via `.NewMock()` and attach a query pattern which will be used to catch a matched query. `.WithReply()` specifies which response will be provided during the mock of this request.
As `Catcher` is global variable without calling `.Reset()` this mock will be applied to all subsequent tests and queries if the pattern matches.

### Usage via `FakeResponse` object
## Usage via `FakeResponse` Object

We will take `GetUsers` from the previous example to show how could be used attachment of mock directly to Catcher object
We are taking `GetUsers` from the previous example and an example on how it can be using a FakeResponse directly attached to the Catcher object

```go
t.Run("Simple select with direct object", func(t *testing.T) {
Catcher.Reset()
commonReply := []map[string]interface{}{{"user_id": 27, "name": "FirstLast", "age": "30"}}
Catcher.Attach([]*FakeResponse{
{
Pattern:"SELECT name FROM users WHERE", // the same as .WithQuery()
Expand All @@ -100,57 +103,67 @@ t.Run("Simple select with direct object", func(t *testing.T) {
})
```

### GORM example
## GORM Example

Usage of a mocked GORM is completely transparent. You need to know which query will be generated by GORM and mock them or mock just by using arguments. In this case, you need to pay attention to order of arguments as GORM will not necessarily arrange them in order you provided them.

*Tip:* To identify the exact query generated by GORM you can look at the console output when running your mocked DB connection. They show up like this:
```
2018/01/01 12:00:01 mock_catcher: check query: INSERT INTO "users" ("name") VALUES (?)
```


Usage of GORM is completely the same as it would be without it. You need to understand which query will be generated by GORM and mock or mock just by arguments. In this case, you need to pay attention to order of arguments as GORM will arrange them not in order you can provide them
## More Examples

***

### Catch by arguments
### Catch by Arguments

The query could be caught by provided arguments even without specifying query pattern to match.
Please notice 2 important facts:
The query can be caught by provided arguments even you are not specifying a query pattern to match.
Please note these two important facts:

* Order is very important
* GORM will re-order arguments according to fields in struct defined to describe you models.
* GORM will re-order arguments according to fields in the struct defined to describe your model.

```go
t.Run("Catch by arguments", func(t *testing.T) {
commonReply := []map[string]interface{}{{"name": "FirstLast", "age": "30"}}
Catcher.Reset().NewMock().WithArgs(int64(27)).WithReply(commonReply)
result := GetUsers(DB)
if len(result) != 1 {
t.Fatalf("Returned sets is not equal to 1. Received %d", len(result))
}
// all other check from reply
// all other checks from reply
})
```

### Match only once
### Match Only Once

Mock marked as only once and do not return next time
Mocks marked as Once, will not be match on subsequent queries.
```go
t.Run("Once", func(t *testing.T) {
Catcher.Reset()
Catcher.Attach([]*FakeResponse{
{
Pattern:"SELECT name FROM users WHERE",
Response: commonReply,
Once: true, // could be done via chaining .OneTime()
},
})
GetUsers(DB) // Trigger once to use this mock
result := GetUsers(DB) // trigger second time to receive empty results
if len(result) != 0 {
t.Errorf("Returned sets is not equal to 0. Received %d", len(result))
}
Catcher.Reset()
Catcher.Attach([]*FakeResponse{
{
Pattern:"SELECT name FROM users WHERE",
Response: commonReply,
Once: true, // could be done via chaining .OneTime()
},
})
GetUsers(DB) // Trigger once to use this mock
result := GetUsers(DB) // trigger second time to receive empty results
if len(result) != 0 {
t.Errorf("Returned sets is not equal to 0. Received %d", len(result))
}
})
```

### Insert Id with `.WithId(int64)`
### Insert ID with `.WithId(int64)`

In order to emulate `INSERT` requests, we can mock ID returned with `.WithId(int64)` method.
In order to emulate `INSERT` requests, we can mock the ID returned from the query with the `.WithId(int64)` method.

```go
//Somewhere in the code
// Somewhere in the code
func InsertRecord(db *sql.DB) int64 {
res, err := db.Exec(`INSERT INTO foo VALUES("bar", ?))`, "value")
if err != nil {
Expand All @@ -172,12 +185,12 @@ t.Run("Last insert id", func(t *testing.T) {
})
```

### Emulate exceptions
### Emulate Exceptions

You could emulate exceptions or error during the request by saying it fake `FakeResponse` object.
Please notice that to fire error on `SELECT` you need to use `WithQueryException()`, for queries which do not return results you need to use `.WithExecException()`
You can emulate exceptions or errors during the request by setting it with a fake `FakeResponse` object.
Please note that to fire an error on `SELECT` you need to use `WithQueryException()`, for other queries (UPDATE, DELETE, etc) which do not return results, you need to use `.WithExecException()`.

Example:
Example:
```go
// Somewhere in the code
func GetUsersWithError(db *sql.DB) error {
Expand Down Expand Up @@ -209,8 +222,53 @@ t.Run("Fire Execute error", func(t *testing.T) {
}
})
```

### Callbacks

Besides that, you can catch and attach callbacks when mock is used
Besides that, you can catch and attach callbacks when the mock is used.

## Code Gotchas

### Query Matching
When you try to match against a query, you have to make sure that you do so with precision.

For example, this query :
```sql
SELECT * FROM "users" WHERE ("users"."user_id" = 3) ORDER BY "users"."user_id" ASC LIMIT 1
```

If you try to match it with this:
```go
Catcher.Reset().NewMock().WithQuery(`SELECT * FROM users WHERE`).WithReply(commonReply)
```
It will not work for two reasons. `users` is missing double-quotes and there are two spaces before `WHERE`. One trick is to actually run the test and look at the mocked DB output to find the exact query being executed.
Here is the right way to match this query:

```go
Catcher.Reset().NewMock().WithQuery(`SELECT * FROM "users" WHERE`).WithReply(commonReply)
```

### Reply Matching
When you provide a Reply to Catcher, your field names must match your database model or else, they will not be updated with the right value.

Given you have this test code:
```go
// DO NOT USE, CODE NOT WORKING
commonReply := []map[string]interface{}{{"userID": 7, "name": "FirstLast", "age": "30"}}
mocket.Catcher.NewMock().OneTime().WithQuery(`SELECT * FROM "dummies"`).WithReply(commonReply)

result := GetUsers(DB)
```

This will work and not error out, but `result` will have a 0 value in the field `userID`. You must make sure to match the Reply fields with the database fields and not the struct fields or else you might bang your head on your keyboard.

The following code works:
```go
commonReply := []map[string]interface{}{{"user_id": 7, "name": "FirstLast", "age": "30"}}
mocket.Catcher.NewMock().OneTime().WithQuery(`SELECT * FROM "dummies"`).WithReply(commonReply)

result := GetUsers(DB)
```

__More examples coming....__

__More examples coming....__

0 comments on commit e3c3b4f

Please sign in to comment.