In this article, we are going to test our REST API by using the HTTP test library and we are going to use SQLite 3 as the database.

Remember on a previous article we applied a clean architecture approach to build a Restful API, where we created two endpoints, one to create new and one in order to get existing posts. And this approach allowed us to swap between different database, we can use Firestore, MySQL, SQLite… And we can swap between different HTTP frameworks, in this case we are able to use CHI or we can use MUX or any other HTTP frameworks . And this approach also enables a better the stability of our application so we created a test in our business logic, so we mocked the repository and we were able to isolate the business layer to test it.

SQLite PostRepository implementation

We are going to use SQLite so we need a new repository implementation of SQLite for the PostRepository interface

type PostRepository interface {
	Save(post *entity.Post) (*entity.Post, error)
	FindAll() ([]entity.Post, error)
	Delete(post *entity.Post) error
}

First we need to install SQLite library

$ go get -u github.com/mattn/go-sqlite3

Then we create the SQLiteRepository which is the implementation of SQL for the PostRepository interface in the file repository/sqlite-repo.go

package repository

import (
	"database/sql"
	"log"
	"os"

	"golang-mux-api/entity"

	_ "github.com/mattn/go-sqlite3"
)

type sqliteRepo struct{}

func NewSQLiteRepository() PostRepository {
	os.Remove("./posts.db")

	db, err := sql.Open("sqlite3", "./posts.db")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	sqlStmt := `
	create table posts (id integer not null primary key, title text, txt text);
	delete from posts;
	`
	_, err = db.Exec(sqlStmt)
	if err != nil {
		log.Printf("%q: %s\n", err, sqlStmt)
	}
	return &sqliteRepo{}
}

func (*sqliteRepo) Save(post *entity.Post) (*entity.Post, error) {
	db, err := sql.Open("sqlite3", "./posts.db")
	if err != nil {
		log.Fatal(err)
		return nil, err
	}

	tx, err := db.Begin()
	if err != nil {
		log.Fatal(err)
		return nil, err
	}
	stmt, err := tx.Prepare("insert into posts(id, title, txt) values(?, ?, ?)")
	if err != nil {
		log.Fatal(err)
		return nil, err
	}
	defer stmt.Close()
	_, err = stmt.Exec(post.ID, post.Title, post.Text)
	if err != nil {
		log.Fatal(err)
		return nil, err
	}

	tx.Commit()
	return post, nil
}

func (*sqliteRepo) FindAll() ([]entity.Post, error) {
	db, err := sql.Open("sqlite3", "./posts.db")
	if err != nil {
		log.Fatal(err)
		return nil, err
	}

	rows, err := db.Query("select id, title, txt from posts")
	if err != nil {
		log.Fatal(err)
		return nil, err
	}
	defer rows.Close()
	var posts []entity.Post
	for rows.Next() {
		var id int64
		var title string
		var text string
		err = rows.Scan(&id, &title, &text)
		if err != nil {
			log.Fatal(err)
			return nil, err
		}
		post := entity.Post{
			ID:    id,
			Title: title,
			Text:  text,
		}
		posts = append(posts, post)
	}
	err = rows.Err()
	if err != nil {
		log.Fatal(err)
		return nil, err
	}
	return posts, nil
}

func (*sqliteRepo) Delete(post *entity.Post) error {
	db, err := sql.Open("sqlite3", "./posts.db")
	if err != nil {
		log.Fatal(err)
		return err
	}

	tx, err := db.Begin()
	if err != nil {
		log.Fatal(err)
		return err
	}
	stmt, err := tx.Prepare("delete from posts where id = ?")
	if err != nil {
		log.Fatal(err)
		return err
	}
	defer stmt.Close()
	_, err = stmt.Exec(post.ID)
	if err != nil {
		log.Fatal(err)
		return err
	}
	tx.Commit()
	return nil
}

Run and verify SQLite implementation

To start the REST API

$ go run *.go

Mux HTTP server running on port :8000

Let’s try to create some new posts

curl -v --location 'http://localhost:8000/posts' \
--header 'Content-Type: application/json' \
--data '{
    "title": "Title 1",
    "text": "Text 1"
}'
{"id": 8936326456275005323,"title": "Title 1","text": "Text 1"
}

$ curl --location 'http://localhost:8000/posts' \
--header 'Content-Type: application/json' \
--data '{
    "title": "Title 2",
    "text": "Text 2"
}'
{"id":8802839608357740770,"title":"Title 2","text":"Text 2"}

You can verify the results by openning the SQLite database from the file ./posts.db

You can also verify the results by calling the REST API to get all existing posts

$ curl -v --location 'http://localhost:8000/posts'
*   Trying 127.0.0.1:8000...
* Connected to localhost (::1) port 8000 (#0)
> GET /posts HTTP/1.1
> Host: localhost:8000
< HTTP/1.1 200 OK
< Content-Type: application/json
< Content-Length: 124
[{"id":8802839608357740770,"title":"Title 2","text":"Text 2"},{"id":8936326456275005323,"title":"Title 1","text":"Text 1"}]

Create test for the controller

We are going to create new test for our controller, by creating new file called post-controller_test.go and we are going to use the HTTP test library and SQLite as database, and we are going to create basically two tests TestAddPost and TestGetPosts

package controller

import (
	"bytes"
	"encoding/json"
	"golang-rest-api/entity"
	"golang-rest-api/repository"
	"golang-rest-api/service"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/assert"
)

const (
	ID    int64  = 123
	TITLE string = "Title 1"
	TEXT  string = "Text 1"
)

var (
	postRepo       repository.PostRepository = repository.NewSQLiteRepository()
	postSrv        service.PostService       = service.NewPostService(postRepo)
	postController PostController            = NewPostController(postSrv)
)

func TestAddPost(t *testing.T) {
	// Create a new HTTP POST request
	var jsonReq = []byte(`{"title": "Title 1","text": "Text 1"}`)
	req, _ := http.NewRequest("POST", "/posts", bytes.NewBuffer(jsonReq))

	// Assign HTTP Handler function (controller AddPost function)
	handler := http.HandlerFunc(postController.AddPost)

	// Record HTTP Response (httptest)
	response := httptest.NewRecorder()

	// Dispatch the HTTP request
	handler.ServeHTTP(response, req)

	// Add Assertions on the HTTP Status code and the response
	status := response.Code

	if status != http.StatusOK {
		t.Errorf("Handler returnd a wrong status code: got %v want %v", status, http.StatusOK)
	}

	// Decode the HTTP response
	var post entity.Post
	json.NewDecoder(io.Reader(response.Body)).Decode(&post)

	// Assert HTTP response
	assert.NotNil(t, post.ID)
	assert.Equal(t, TITLE, post.Title)
	assert.Equal(t, TEXT, post.Text)

	// Clean up database
	cleanUp(&post)
}

func TestGetPosts(t *testing.T) {
	// Insert new post
	setup()

	// Create a GET HTTP request
	req, _ := http.NewRequest("GET", "/posts", nil)

	// Assign HTTP Handler function (controller GetPosts function)
	handler := http.HandlerFunc(postController.GetPosts)

	// Record HTTP Response (httptest)
	response := httptest.NewRecorder()

	// Dispatch the HTTP request
	handler.ServeHTTP(response, req)

	// Add Assertions on the HTTP Status code and the response
	status := response.Code

	if status != http.StatusOK {
		t.Errorf("Handler returnd a wrong status code: got %v want %v", status, http.StatusOK)
	}

	// Decode the HTTP response
	var posts []entity.Post
	json.NewDecoder(io.Reader(response.Body)).Decode(&posts)

	// Assert HTTP response
	assert.NotNil(t, posts[0].ID)
	assert.Equal(t, TITLE, posts[0].Title)
	assert.Equal(t, TEXT, posts[0].Text)

	// Clean up database
	cleanUp(&posts[0])
}

func cleanUp(post *entity.Post) {
	postRepo.Delete(post)
}

func setup() {
	var post entity.Post = entity.Post{
		ID:    ID,
		Title: TITLE,
		Text:  TEXT,
	}
	postRepo.Save(&post)
}

Run and verify test cases

First run the TestAddPost test case

$ cd controller/
$ go test -v -run TestAddPost
=== RUN   TestAddPost
--- PASS: TestAddPost (0.01s)
PASS
ok      golang-mux-api/controller       0.031s

Then run the TestGetPosts test case

$ go test -v -run TestGetPosts
=== RUN   TestGetPosts
--- PASS: TestGetPosts (0.01s)
PASS
ok      golang-mux-api/controller       0.037s

Download Source Code

$ git clone https://github.com/favtuts/golang-mux-api.git
$ cd golang-mux-api

$ git checkout rest-api-testing
$ go build

$ go run .

Leave a Reply

Your email address will not be published. Required fields are marked *