Integration tests for this blog
Published: 08 Feb 25 11:44 UTC
Last Updated: 08 Feb 25 11:46 UTC
Breaking with tradition to write a technical post.
I've been developing the blog functionality of this website for a few weeks now.
One of the challenges I encountered was to write a series of decent "integration" tests.
I've structured my application with MVC, so I needed to ensure the database logic (models) were interacting correctly with the controller and rendering the correct items in the view.
I have written this entire application in Golang. Two key tools I found incredibly useful were:
mockery
Mockery is an amazing tool that lets you stub entire components of your application, provided they are defined as interfaces and not structs.
In my use case this is very useful for mocking errors being returned by the database to test out the error handling code.
E.g. I define the following in .mockery.yaml
(the mockery configuration file)
1quiet: false
2with-expecter: true
3disable-version-string: true
4all: true
5mockname: "Mock{{.InterfaceName}}"
6filename: "{{.InterfaceNameSnake}}.go"
7keeptree: false
8packages:
9 github.com/fugu-chop/blah/blah/blah:
10 config:
11 dir: "./test/pkg/modeltest"
12 outpkg: "modeltest"
13 interfaces:
14 # PostService is my db/model logic for blog posts
15 PostService:
I run mockery
from the CLI, and this generates typed code that mocks the response of the PostService
.
I can then simulate error responses in my integration tests:
1func TestPublish_PostServiceCreate_Error(t *testing.T) {
2 postService := modeltest.NewMockPostService(t)
3 // this EXPECT() method both asserts that the `Create` method will be called
4 // AND specifies the behaviour to occur when `Create` is called
5 postService.EXPECT().Create(mock.Anything).Return(errors.New("something went wrong"))
6 mdParser := goldmark.New()
7 c := controllers.NewPostsController(postService)
8
9 // Rest of test
10}
dockertest
Mockery is most useful for stubbing out components for error scenarios.
However, the majority of happy paths involve some testing of the database - e.g. that a record is written to the database on a Create
method call.
Enter dockertest
.
This package allows you to spin up a docker container containing a database engine of your choice (I use Postgres). It allows you to establish the connection, run your tests, and it cleans up after itself.
The documentation is very well written. The only issues I had were idiosyncrasies of my personal machine and implementation. Specifically:
-
My local installation of Postgres (via
brew
) was corrupted, such that the specified ports never seemed to be open - when tests were run, my server could never communicate with the Postgres container spun up bydockertest
. Abrew reinstall
fixed this. -
I originally tried to use a
pgx
(a Postgres driver)-defined database URL to connect to the test database (used to run migrations). This was in order to mimic the actual implementation. Howeverdockertest
did not like the format used bypgx
and would continually complain about invalid or missing parameters. I eventually gave up and simply used the out-of-box Postgres examples provided.
Both of these tools were very useful in giving me good confidence in testing out the blog.
Useful Readings
-
Mockery Repo: How to use the
mockery
functionality. -
dockertest
Repo: Well fleshed out examples of how to use the package with different drivers.