# Tutorial: Repository pattern in Golang with Test Driven Development

In the last [blog post](https://blog.gkomninos.com/tutorial-implementing-repository-with-gorm-and-sqlite?source=more_series_bottom_blogs) I showed you how you can implement the repository pattern in Golang. We used [SQLite](https://sqlite.org/) and [GORM](https://gorm.io/index.html) to implement the `CompanyRepository` .

Today I am going to show you how to implement the `InvoiceRepository` .  
The implementation will be very similar but today we are going to start first by writing unit tests and then the actual implementation.

### Create a new branch

We will work in a new git branch. Let's create this:

```bash
git checkout -b invoice-repo
```

### Our interface

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718690045267/b0768862-3a7b-4c83-8fef-fcf2af7728d1.png align="center")

We have to implement a struct that implements these three methods:

* `Create` : Inserts an invoice in the database and returns it's id
    
* `Get`: Fetches an invoices from the database using its id
    
* `GetLastInvoiceForYear`: Returns the last invoice from the provided year.
    

### Writing a test first

> In which package are we going to write our tests?

In our project layout we have a package `sqlite` . This is where we are going to write our code.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718690323826/b6b8448b-b09f-4594-851d-5d06f6280c3a.png align="center")

Now create a new file named `invoice_test.go` .

Let's write our tests:

```go
package sqlite_test

import (
	"testing"
)

func Test_invoiceRepository(t *testing.T) {
}
```

We don't test anything so far but let's verify that we can run our test:

```bash
go test -v ./... -run=Test_invoiceRepository
```

the output should be similar to:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718690716752/1290c071-5bea-4951-8416-c85a99a05db8.png align="center")

### Provide a dummy implementation of the interface

Create a file `sqlite/invoice.go` with contents:

```go
package sqlite

import (
	"context"
	"invoicehub"

	"gorm.io/gorm"
)

var _ invoicehub.InvoiceRepository = &invoiceRepository{}

type invoiceRepository struct {
	db *gorm.DB
}

func NewInvoiceRepository(db *gorm.DB) invoicehub.InvoiceRepository {
	return &invoiceRepository{db}
}

func (r *invoiceRepository) Create(ctx context.Context, invoice *invoicehub.Invoice) (int, error) {
	return 0, nil
}

func (r *invoiceRepository) Get(ctx context.Context, id int) (invoicehub.Invoice, error) {
	return invoicehub.Invoice{}, nil
}

func (r *invoiceRepository) GetLastInvoiceForYear(ctx context.Context, year int) (invoicehub.Invoice, error) {
	return invoicehub.Invoice{}, nil
}
```

We only wrote the required methods but they are empty.

We now create our tests:

```go
func compareInvoice(t *testing.T, a, b invoicehub.Invoice) {
	require.Equal(t, a.ID, b.ID, "ID mismatch")
	require.Equal(t, a.InvoiceNumber, b.InvoiceNumber, "InvoiceNumber mismatch")
	require.Equal(t, a.IssueDate, b.IssueDate, "IssueDate mismatch")
	require.Equal(t, a.SellerID, b.SellerID, "SellerID mismatch")
	require.Equal(t, a.BuyerID, b.BuyerID, "BuyerID mismatch")
	require.Equal(t, a.DaysToPay, b.DaysToPay, "DaysToPay mismatch")

	require.Len(t, a.LineItems, len(b.LineItems), "LineItems length mismatch")

	for i := range a.LineItems {
		require.Equal(t, a.LineItems[i].Description, b.LineItems[i].Description, "Description mismatch")
		require.Equal(t, a.LineItems[i].Amount.Currency, b.LineItems[i].Amount.Currency, "Currency mismatch")
		require.Equal(t, a.LineItems[i].Amount.Value.String(), b.LineItems[i].Amount.Value.String(), "Amount value mismatch")
	}
}


func Test_invoiceRepository(t *testing.T) {
	db, err := sqlite.SetupDB(":memory:")
	require.NoError(t, err)

	repo := sqlite.NewInvoiceRepository(db)
	require.NotNil(t, repo)

	ctx := context.Background()

	invoice := invoicehub.Invoice{
		InvoiceNumber: "1001/24",
		IssueDate:     time.Date(2024, 1, 2, 0, 0, 0, 0, time.UTC),
		SellerID:      1,
		BuyerID:       2,
		DaysToPay:     14,

		LineItems: []invoicehub.LineItem{
			{
				Description: "My awesome services",
				Amount: invoicehub.Amount{
					Value:    decimal.NewFromFloat(100.00),
					Currency: "EUR",
				},
				VatRate: decimal.NewFromFloat(0.19),
			},
		},
	}

	// Create a new invoice
	id, err := repo.Create(ctx, &invoice)
	require.NoError(t, err)
	require.NotZero(t, id)
	require.Equal(t, id, invoice.ID)

	// fetch the invoice

	invoice2, err := repo.Get(ctx, id)
	require.NoError(t, err)
	compareInvoice(t, invoice, invoice2)

	// fetch a non-existing invoice
	_, err = repo.Get(ctx, 999)
	require.Error(t, err)

	// add one more invoice
	invoice3 := invoicehub.Invoice{
		InvoiceNumber: "1002/24",
		IssueDate:     time.Date(2024, 2, 2, 0, 0, 0, 0, time.UTC),
		SellerID:      1,
		BuyerID:       2,
		DaysToPay:     14,

		LineItems: []invoicehub.LineItem{
			{
				Description: "web development",
				Amount: invoicehub.Amount{
					Value:    decimal.NewFromFloat(100.00),
					Currency: "EUR",
				},
				VatRate: decimal.NewFromFloat(0.19),
			},
			{
				Description: "web scraping",
				Amount: invoicehub.Amount{
					Value:    decimal.NewFromFloat(150.00),
					Currency: "EUR",
				},
				VatRate: decimal.NewFromFloat(0.19),
			},
		},
	}

	id3, err := repo.Create(ctx, &invoice3)
	require.NoError(t, err)
	require.NotZero(t, id3)
	require.Equal(t, id3, invoice3.ID)

	// fetch the last invoice for the year 2024

	lastInvoice, err := repo.GetLastInvoiceForYear(ctx, 2024)
	require.NoError(t, err)
	compareInvoice(t, invoice3, lastInvoice)
}
```

In the above test:

* create an invoice
    
* assert that our function returns a newly generated id
    
* assert that the id generated is also set in our Invoice struct
    
* try to fetch the invoice with that id
    
* try to fetch a non existing invoice
    
* create another invoice for a later date
    
* assert that the last invoice of the year is the last created
    

> We are using the package decimal.Decimal and testify package does not play well with it (bacause the way it uses reflection I think) . Thus, we created a custom compareInvoice function that performs the assertions

now run the test again:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718691691450/e4f1fe1c-5d02-486f-aa88-6ce38cc2851e.png align="center")

As expected our test fails . We don't have any implementation yet.

**Separate our domain entity (invoicehub.Invoice) from the database models**

In `sqlite/invoice.go` let's define the required struct that reassembles our database table:

```go
type dbinvoice struct {
	ID            int       `gorm:"primaryKey"`
	InvoiceNumber string    `gorm:"type:text"`
	IssueDate     time.Time `gorm:"type:datetime"`
	BuyerID       int
	SellerID      int
	DaysToPay     int

	LineItems datatypes.JSONSlice[invoicehub.LineItem]
}
```

above we defined the db model (the table will be generated from that).

In order for Gorm to take care of the table creation modify the `sqlite/sqlite.go` .

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718692694835/50dcb67f-d235-45ac-a547-960e8273a0f2.png align="center")

> an attentive reader will have already noticed that the sellerID and buyerID fields do not have foreign keys that reference the dbcompanies table.

We will leave these out for now. However this might lead to inconsistencies in our data at some point.

**Implement the Create method**

```go
func (r *invoiceRepository) Create(ctx context.Context, invoice *invoicehub.Invoice) (int, error) {
	dbInvoice := dbinvoice{
		InvoiceNumber: invoice.InvoiceNumber,
		IssueDate:     invoice.IssueDate,
		BuyerID:       invoice.BuyerID,
		SellerID:      invoice.SellerID,
		DaysToPay:     invoice.DaysToPay,
		LineItems:     datatypes.NewJSONSlice(invoice.LineItems),
	}

	if err := r.db.WithContext(ctx).Create(&dbInvoice).Error; err != nil {
		return 0, err
	}

	invoice.ID = dbInvoice.ID

	return invoice.ID, nil
}
```

**Implement the Get method**

```go
func (r *invoiceRepository) Get(ctx context.Context, id int) (invoicehub.Invoice, error) {
	var dbitem dbinvoice
	if err := r.db.WithContext(ctx).First(&dbitem, id).Error; err != nil {
		return invoicehub.Invoice{}, err
	}

	invoice := invoicehub.Invoice{
		ID:            dbitem.ID,
		InvoiceNumber: dbitem.InvoiceNumber,
		IssueDate:     dbitem.IssueDate,
		BuyerID:       dbitem.BuyerID,
		SellerID:      dbitem.SellerID,
		DaysToPay:     dbitem.DaysToPay,
		LineItems:     dbitem.LineItems,
	}

	return invoice, nil
}
```

**Implement the GetLastInvoiceForYear**

```go
func (r *invoiceRepository) GetLastInvoiceForYear(ctx context.Context, year int) (invoicehub.Invoice, error) {
	query := r.db.WithContext(ctx).
		Where("strftime('%Y', issue_date) = ?", strconv.Itoa(year)).
		Order("issue_date desc").
		Limit(1)

	var dbitem dbinvoice
	if err := query.First(&dbitem).Error; err != nil {
		return invoicehub.Invoice{}, err
	}

	invoice := invoicehub.Invoice{
		ID:            dbitem.ID,
		InvoiceNumber: dbitem.InvoiceNumber,
		IssueDate:     dbitem.IssueDate,
		BuyerID:       dbitem.BuyerID,
		SellerID:      dbitem.SellerID,
		DaysToPay:     dbitem.DaysToPay,
		LineItems:     dbitem.LineItems,
	}

	return invoice, nil
}
```

This one is a little bit more tricky.

We utilize the `strftime` of SQLite in order to fetch all invoices with issue date in the provided year.

> We don't have an index in the IssueDate column. This query might be slow when we have a lot of data.

But, since this web application will be only for one user we will skip creating index beforehand. We will do this when it's required. But it's good to keep that in mind.

Now we run our tests again:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718694275295/664e8b4b-9e99-49b3-9c9a-822d2f4881d0.png align="center")

It looks we are good so let's commit

```go
git add .
git commit -m "implements invoice repository"
```

You can find today's version in the github branch:

[github invoice-repo branch](https://github.com/gosom/freelance-invoice-hub/tree/invoice-repo)

### Conclusion

This blog posted shows the reader how to implement the repository pattern in Go using GORM and SQLite.

We seen how we can write unittests first for our database related code and then complete the implementation.

We once again flagged the need to separate your database models with your domain entities.

❤️ If you read the article until here and liked it please hit the like button.

👉 I would love you to follow me on [X](https://x.com/gkomdev) or [LinkedIn](http://www.linkedin.com/in/georgios-komninos-172508147)

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">What challenges have you faced while implementing the repository pattern in your projects?</div>
</div>
