# Crafting a Web app in Golang: Implementing the Service Layer

Tutorial: golang web application development

Hello all, today we will continue the development of our [web application](https://blog.gkomninos.com/series/webapp-using-golang) by implementing the part of invoice management.

We are going to implement the `InvoiceService` interface. We will learn also how we can use mocks to unit test our service.

## The interface

For managing the invoices we have defined the following interface :

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718775623564/4ce2245c-9c66-4499-b6a0-ca8e23bca4da.png align="center")

The interface has 3 methods:

* Create: this method is responsible to create a new invoice
    
* Get: returns the invoice with id
    
* CreatePDF: Given an invoice id returns a PDF document
    

### Let's get started

Create a new git branch

```bash
git checkout -b invoice-svc
```

Create a new package named invoices

```bash
mkdir invoices
```

and a file `invoices/invoices.go` .

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718776220078/7e66a43f-255b-4c5c-8ab0-876a3ee93276.png align="center")

and add the following contents in the newly created `invoices.go` .

```go
package invoices

import (
	"context"
	"invoicehub"
)

var _ invoicehub.InvoiceService = (*invoiceSvc)(nil)

type invoiceSvc struct {
	repo invoicehub.InvoiceRepository
}

func New(repo invoicehub.InvoiceRepository) invoicehub.InvoiceService {
	return &invoiceSvc{repo}
}

func (svc *invoiceSvc) Create(ctx context.Context, invoice *invoicehub.Invoice) error {
	return nil
}

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

func (svc *invoiceSvc) CreatePDF(ctx context.Context, id int) ([]byte, error) {
	return nil, nil
}
```

We haven't done much here actualy, just created a struct that implements the interface.

Let's now create the a file that we will add our tests:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718776451255/484351d4-3831-49ed-81b7-03791b54d918.png align="center")

and let's add placeholder for our tests:

```go
package invoices_test

import "testing"

func Test_New(t *testing.T) {
}

func Test_invoiceSvc_Create(t *testing.T) {
}

func Test_invoiceSvc_Get(t *testing.T) {
}

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

### How to use mocks

In order to write our tests we need to be able to provide a "mock" implementation of the `InvoiceRepository` . This will help us to test the behavior of our implementation without having to rely on a real database.

When we created our project layout we created a package called `mocks` that we haven't used so far.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718776853181/b464fa7a-8297-4e3d-85b0-d5e22ff725e4.png align="center")

We are going to utilize the package [mock](https://github.com/uber-go/mock) and `go generate` to create mocks for our interfaces.

**Setting up mock generator**

first install the required package:

```bash
go install go.uber.org/mock/mockgen@latest
go get go.uber.org/mock/mockgen/model
```

and add the following in our Makefile:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718777210912/cc7fa621-14f0-41a4-8b5d-3bb5d3d5dbf5.png align="center")

Now we can add a special comment line above the definition of the interfaces we want to mock.

The comment has the following format:

```go
//go:generate mockgen -destination=mocks/mock_*.go -package=mocks . InternfaceName
```

So let's open the file `invoices.go` and above the definition of the `InvoiceRepository`:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718777434120/c39e2797-7490-4c4e-8570-84721b81d414.png align="center")

Now run :

```bash
make gen
```

this will generate a file: `mocks/mock_invoice_repo.go`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718777565433/53856a01-714d-4b70-9b8e-d79a4642e50d.png align="center")

If you open the file you will see that it generated a struct with name `MockInvoiceRepository` that implments the `InvoiceRepository` interface .

Each time you add a method in the `InvoiceRepository` you have to run the `make gen` command to re-generate the mock implementation.

Now let's open the file `` invoices/invoices_test.go` `` and add the following:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718778002008/07eba03a-5209-4e80-bef2-cb760d7bfbe6.png align="center")

the above shows how we were able to initialize the a invoice service using a mock implementation of the InvoiceRepository.

### Unit Tests and Implementation

We will now implement our interface's methods one by one.

We start from the `Create` method.

Our create method should work as following:

we will pass an `Invoice` struct as an argument and this method will be responsible to :

calculate the `InvoiceNumber` based on the `IssueDate` and the last invoice Number of the current year. If an invoice does not exist for the current year we start with invoice number 1001 😄 .

Then we should save the newly created invoice in the database .

Let's do that :

```go
func (svc *invoiceSvc) Create(ctx context.Context, invoice *invoicehub.Invoice) error {
	issueYear := invoice.IssueDate.Year()

	lastInvoice, err := svc.repo.GetLastInvoiceForYear(ctx, issueYear)
	if err != nil {
		return err
	}

	parts := strings.Split(lastInvoice.InvoiceNumber, "/")
	if len(parts) != 2 {
		return errors.New("invalid invoice number")
	}

	lastInvoiceNumber, err := strconv.Atoi(parts[0])
	if err != nil {
		return err
	}

	invoice.InvoiceNumber = fmt.Sprintf("%d/%d", lastInvoiceNumber+1, issueYear)

	if _, err := svc.repo.Create(ctx, invoice); err != nil {
		return err
	}

	return nil
}
```

Now we have to write some tests to see if this works

in your `invoices/invoices_test.go` :

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718778994264/bdba7d27-451e-4852-b8d2-4175a79f6cb5.png align="center")

Here I have added some test cases . This is not 100% covers all the cases but it's good enough for now.

Let's first implement the first case:

```go
	t.Run("when there is no last invoice for the current year", func(t *testing.T) {
		inv := &invoicehub.Invoice{
			IssueDate: time.Date(2024, 1, 2, 0, 0, 0, 0, time.UTC),
		}

		err := svc.Create(context.Background(), inv)
		require.NoError(t, err)
	})
```

Now let's run the test and see what it will happen:

```bash
go test -v ./... -run=Test_invoiceSvc_Create/when_there_is_no_last_invoice_for_the_current_year
```

and we get:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718779300080/3e1a0adc-a603-4b8f-8930-69438c1d629e.png align="center")

The error tells us that our method called the method:  
`*mocks.MockInvoiceRepository.GetLastInvoiceForYear`

and this was not expected .

We need to instruct the test what the call to this method will return .

<mark>But wait a second...What our repository returns when a record is not found in the database?</mark>

It returns a [gorm.ErrNotFound](https://gorm.io/docs/error_handling.html#ErrRecordNotFound) error . We could return that BUT this will leak implemetation details to our service layer. We have to define a custom error in our domain that we can utilize.

Open the `invoice.go` and add on top:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718779870957/7471065c-62b8-41e7-9385-ffa88ac0bc00.png align="center")

Now open the `sqlite/invoice.go` and return that error when an invoice is not found.  
We have to do that in two places:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718779919807/cb3ceb4a-fc2d-4b93-bfef-295ed8272a45.png align="center")

Let's also modify the InvoiceRepository's tests to check that when an invoice is not found it returns that error:

In the `func Test_invoiceRepository(t *testing.T) {`

when an invoice is not found assert that the error is of this type:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718780026570/adc653ba-0f8f-4100-b382-41d632027090.png align="center")

and in the bottom:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718780081049/9b91f1ce-da36-49b9-ad1d-0a5115299933.png align="center")

Now let's run that test to make sure it works:

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

Now we can proceed with writing the test for the Create method of InvoiceRepository:

```go
	t.Run("when there is no last invoice for the current year", func(t *testing.T) {
		inv := &invoicehub.Invoice{
			IssueDate: time.Date(2024, 1, 2, 0, 0, 0, 0, time.UTC),
		}

		repo.EXPECT().GetLastInvoiceForYear(gomock.Any(), 2024).Return(invoicehub.Invoice{}, invoicehub.ErrInvoiceNotFound)

		err := svc.Create(context.Background(), inv)
		require.NoError(t, err)
	})
```

But our test still fails:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718780340040/aef876e5-53c3-4c83-a724-fc6f81657a0d.png align="center")

Why is that?

in our implementation we do that:  

```go
	lastInvoice, err := svc.repo.GetLastInvoiceForYear(ctx, issueYear)
	if err != nil {
		return err
	}
```

**But, when there is no invoice in the database we return an ErrInvoiceNotFound.**

In our requirements we have that when a invoice for this year does not exist then we should generate a new invoice number starting with 1001.

Let's fix it:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718780654512/c3a294f4-970a-4a3d-ab85-ec6fd43d354e.png align="center")

So now we check for that error and create an invoiceNumber as we need.

But again the test still fail 😟

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718780722528/16b96781-5e15-4a2f-83b5-159d516adfd4.png align="center")

Ok , this is good actually . We know what is going on. We just call the repo.Create method but we don't mock it as above .

The final code for that test case becomes:

```go
	t.Run("when there is no last invoice for the current year", func(t *testing.T) {
		inv := &invoicehub.Invoice{
			IssueDate: time.Date(2024, 1, 2, 0, 0, 0, 0, time.UTC),
		}

		repo.EXPECT().GetLastInvoiceForYear(gomock.Any(), 2024).Return(invoicehub.Invoice{}, invoicehub.ErrInvoiceNotFound)
		repo.EXPECT().Create(gomock.Any(), inv).Return(1, nil)

		err := svc.Create(context.Background(), inv)
		require.NoError(t, err)
	})
```

Now let's quickly complete the remaining test cases.  
Our test now looks like:  

```go
func Test_invoiceSvc_Create(t *testing.T) {
	mctrl := gomock.NewController(t)
	defer mctrl.Finish()

	repo := mocks.NewMockInvoiceRepository(mctrl)

	svc := invoices.New(repo)

	t.Run("when there is no last invoice for the current year", func(t *testing.T) {
		inv := &invoicehub.Invoice{
			IssueDate: time.Date(2024, 1, 2, 0, 0, 0, 0, time.UTC),
		}

		repo.EXPECT().GetLastInvoiceForYear(gomock.Any(), 2024).Return(invoicehub.Invoice{}, invoicehub.ErrInvoiceNotFound)
		repo.EXPECT().Create(gomock.Any(), inv).Return(1, nil)

		err := svc.Create(context.Background(), inv)
		require.NoError(t, err)

		require.Equal(t, "1001/2024", inv.InvoiceNumber)
	})

	t.Run("when there is a last invoice for the current year", func(t *testing.T) {
		inv1 := &invoicehub.Invoice{
			IssueDate:     time.Date(2024, 1, 2, 0, 0, 0, 0, time.UTC),
			InvoiceNumber: "1001/2024",
		}

		invNew := &invoicehub.Invoice{
			IssueDate: time.Date(2024, 2, 5, 0, 0, 0, 0, time.UTC),
		}

		repo.EXPECT().GetLastInvoiceForYear(gomock.Any(), 2024).Return(*inv1, nil)

		repo.EXPECT().Create(gomock.Any(), invNew).Return(2, nil)

		err := svc.Create(context.Background(), invNew)
		require.NoError(t, err)

		require.Equal(t, "1002/2024", invNew.InvoiceNumber)
	})

	t.Run("when there is a database error while getting the last invoice", func(t *testing.T) {
		inv := &invoicehub.Invoice{
			IssueDate: time.Date(2024, 1, 2, 0, 0, 0, 0, time.UTC),
		}

		repo.EXPECT().GetLastInvoiceForYear(gomock.Any(), 2024).Return(invoicehub.Invoice{}, errors.New("something went wrong"))

		err := svc.Create(context.Background(), inv)
		require.Error(t, err)
	})

	t.Run("when we cannot save the invoice", func(t *testing.T) {
		invoice := &invoicehub.Invoice{
			IssueDate: time.Date(2024, 1, 2, 0, 0, 0, 0, time.UTC),
		}

		repo.EXPECT().GetLastInvoiceForYear(gomock.Any(), 2024).Return(invoicehub.Invoice{}, invoicehub.ErrInvoiceNotFound)
		repo.EXPECT().Create(gomock.Any(), invoice).Return(0, errors.New("something went wrong"))

		err := svc.Create(context.Background(), invoice)
		require.Error(t, err)
	})
}
```

Verify that the tests pass:

```bash
go test -v ./...
```

**The Get method**

We have to implement the Get method of the InvoiceService now.

We start again from the unit tests and then we will proceed with the implementation.

```go
func Test_invoiceSvc_Get(t *testing.T) {
	mctrl := gomock.NewController(t)
	defer mctrl.Finish()

	repo := mocks.NewMockInvoiceRepository(mctrl)

	svc := invoices.New(repo)

	t.Run("when the invoice is found", func(t *testing.T) {
		expected := invoicehub.Invoice{
			ID:            1,
			InvoiceNumber: "1001/2024",
			IssueDate:     time.Date(2024, 1, 2, 0, 0, 0, 0, time.UTC),
			SellerID:      1,
			BuyerID:       2,
			DaysToPay:     14,
		}

		repo.EXPECT().Get(gomock.Any(), 1).Return(expected, nil)

		inv, err := svc.Get(context.Background(), 1)
		require.NoError(t, err)

		require.Equal(t, expected, inv)
	})

	t.Run("when the invoice is not found", func(t *testing.T) {
		repo.EXPECT().Get(gomock.Any(), 1).Return(invoicehub.Invoice{}, invoicehub.ErrInvoiceNotFound)

		_, err := svc.Get(context.Background(), 1)
		require.Error(t, err)
		require.ErrorIs(t, err, invoicehub.ErrInvoiceNotFound)
	})

	t.Run("when there is a database error", func(t *testing.T) {
		repo.EXPECT().Get(gomock.Any(), 1).Return(invoicehub.Invoice{}, errors.New("something went wrong"))

		_, err := svc.Get(context.Background(), 1)
		require.Error(t, err)
	})
}
```

The implementation is pretty straightforward here we just call the repo Get method.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718781606643/7be84f57-0b33-4027-8e36-6a8b6bb41530.png align="center")

Let's commit our work:

```bash
git add .
git commit -m "partial implementation of the InvoiceService"
```

## Conclusion

In today's blog post we continued the implementation of our [Invoice management web application](https://blog.gkomninos.com/series/webapp-using-golang). The new thing that we discussed today is how to use mocks in golang to assist you with unit testing.

Find all the code in the [github repo](https://github.com/gosom/freelance-invoice-hub/tree/invoice-svc) .

In the next blog post we will continue the implementation of the InvoiceService by adding the required function to generate PDF using golang.

❤️ In case you found this article useful hit the Like button and follow me on [X](https://x.com/gkomdev) .

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">How do you do mock in Go? Please leave a comment</div>
</div>
