github.com/willyham/dosa@v2.3.1-0.20171024181418-1e446d37ee71+incompatible/examples/testing/README.md (about)

     1  # Examples: Testing
     2  
     3  The example here demonstrates how to write tests for code that uses the DOSA
     4  client. For now, we are suggesting the use of a mock client that we have
     5  generated using [MockGen](https://github.com/golang/mock). It's available
     6  as `(github.com/uber-go/dosa/mocks).MockClient`.
     7  
     8  ## `MockClient` Usage
     9  
    10  Given an entity defined as:
    11  
    12      type User struct {
    13          dosa.Entity `dosa:"primaryKey=UUID"`
    14          UUID        dosa.UUID
    15          Name        string
    16          Email       string
    17          CreatedOn   time.Time
    18      }
    19  
    20  And a method that operates on that entity called `GetUser`:
    21  
    22      type Datastore struct {
    23          client dosa.Client
    24      }
    25  
    26      func (d *Datastore) GetUser(ctx context.Context, uuid dosa.UUID) (*User, error) {
    27          user := &User{uuid: uuid}
    28          readCtx, readCancel := context.WithTimeout(ctx, 1 * time.Second)
    29          if err := d.client.Read(readCts
    30      }
    31  
    32  The `client` behavior can then be mocked using `MockClient`:
    33  
    34      package datastore_test
    35  
    36      import (
    37          "github.com/golang/mock/gomock"
    38          "github.com/stretchr/testify/assert"
    39  
    40          "github.com/uber-go/dosa"
    41          examples "github.com/uber-go/dosa/examples/testing"
    42          "github.com/uber-go/dosa/mocks"
    43      )
    44  
    45      func TestGetUser(t *testing.T) {
    46          ctrl := gomock.NewController(t)
    47          defer ctrl.Finish()
    48  
    49          // mock error from `Read` call
    50          c1 := mocks.NewMockClient(ctrl)
    51          c1.EXPECT().Initialize(gomock.Any()).Return(nil).Times(1)
    52          c1.EXPECT().Read(gomock.Any(), nil, user).Return(errors.New("Read Error")).Times(1)
    53          ds1, _ := examples.NewDatastore(c1)
    54  
    55          u1, err1 := ds1.GetUser(ctx, uuid)
    56          assert.Error(t, err1)
    57          assert.Nil(t, u1)
    58  
    59          // happy path
    60          c2 := mocks.NewMockClient(ctrl)
    61          c2.EXPECT().Initialize(gomock.Any()).Return(nil).Times(1)
    62          c2.EXPECT().Read(gomock.Any(), nil, user).Return(nil).Times(1)
    63          ds2, _ := examples.NewDatastore(c2)
    64  
    65          u2, err2 := ds2.GetUser(ctx, uuid)
    66          assert.NoError(t, err2)
    67          assert.Equal(t, u2, user)
    68      }
    69  
    70  A complete, runnable example of this can be found in our [testing examples package](https://github.com/uber-go/dosa/tree/master/examples/testing).
    71  
    72  ## `EqRangeOp` and `EqScanOp`
    73  
    74  In addition to the `MockClient`, dosa provides two useful `gomock.Matcher`s. `EqRangeOp` allows you to verify
    75  that an expected call to `Range` is made with a specific `RangeOp`. `EqScanOp` does the same thing, except for
    76  the `Scan` function. For instance, assume we have the following entity:
    77  
    78  ```Go
    79  type MenuItem struct {
    80      dosa.Entity `dosa:"primaryKey=((MenuUUID), MenuItemUUID)"`
    81      MenuUUID     dosa.UUID
    82      MenuItemUUID dosa.UUID
    83      Name         string
    84      Description  string
    85  }
    86  ```
    87  
    88  Let's also assume we add the following receiver function to our `DataStore` struct:
    89  ```Go
    90  func (d *Datastore) GetMenu(ctx context.Context, menuUUID dosa.UUID) ([]*MenuItem, error) {
    91  	op := dosa.NewRangeOp(&MenuItem{}).Eq("MenuUUID", menuUUID).Limit(50)
    92  	rangeCtx, rangeCancelFn := context.WithTimeout(ctx, 1*time.Second)
    93  	defer rangeCancelFn()
    94  
    95  	objs, _, err := d.client.Range(rangeCtx, op)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	menuItems := make([]*MenuItem, len(objs))
   101  	for i, obj := range objs {
   102  		menuItems[i] = obj.(*MenuItem)
   103  	}
   104  	return menuItems, nil
   105  }
   106  ```
   107  
   108  In our tests, we could verify that a particular list of `MenuItem` entities were queried for using the `EqRangeOp`
   109  like so:
   110  ```Go
   111  func TestGetMenu(t *testing.T) {
   112      ctrl := gomock.NewController(t)
   113      defer ctrl.Finish()
   114  
   115      expectedOp := dosa.NewRangeOp(&examples.MenuItem{}).Eq("MenuUUID", menuUUID).Limit(50)
   116  
   117      // mock error from Range call
   118      c1 := mocks.NewMockClient(ctrl)
   119      c1.EXPECT().Initialize(gomock.Any()).Return(nil).Times(1)
   120      c1.EXPECT().Range(gomock.Any(), dosa.EqRangeOp(expectedOp)).Return(nil, "", errors.New("Range Error")).Times(1)
   121      ds1, _ := examples.NewDatastore(c1)
   122  
   123      m1, err1 := ds1.GetMenu(ctx, menuUUID)
   124      assert.Error(t, err1)
   125      assert.Nil(t, m1)
   126  
   127      // happy path
   128      c2 := mocks.NewMockClient(ctrl)
   129      c2.EXPECT().Initialize(gomock.Any()).Return(nil).Times(1)
   130      c2.EXPECT().Range(gomock.Any(), dosa.EqRangeOp(expectedOp)).Return(objMenu, "", nil).Times(1)
   131      ds2, _ := examples.NewDatastore(c2)
   132  
   133      m2, err2 := ds2.GetMenu(ctx, menuUUID)
   134      assert.NoError(t, err2)
   135      assert.Equal(t, menu, m2)
   136  }
   137  ```