In general, the question being asked is the testing of dependencies that arise in writing code, and there are two main approaches.
First, mocking, which is to assume the dependency that is being tested will just work, so you create a mock object in your tests to simulate the responses to be as close to the real thing as possible.
Second, is to actually test the dependency by creating a real instance of whatever the dependency is.
I prefer the second when possible because mocks don't capture reality, only an idealized version, so there can be times when your tests pass, but the code breaks.
And sometimes when the tests pass and the code breaks it happens to be in production, which leads to an incident and that is not fun.
Enough talking let us take a look at some examples to understand mocking and dependency creation testing styles.
When to Mock: Example with AWS SDK for GO
Suppose, you've written some GO code that is utilizing the AWS SDK and you want to test it. Because there is no way to recreate the AWS API locally, there aren't many other options besides mocking or making actual API calls to the AWS services.
Although, it may be economically possible, in the beginning of a project to eat the costs of calling the AWS API directly, it is not a scalable practice.
Because there's a point where it is simply too expensive to have call the API directly both during TDD or via executing the tests in the pipeline.
Hence, mocking is a path forward for testing in this scenario.
Let's say our GO code's responsibility is to interact with S3 by putting a file into a bucket and looks like something like this:
then to test it the code might look something like the following:
Luckily, AWS SDK for GO provides interfaces for each of their services so mocking them for testing is easy.
But, as you can see, the only that has been done is too invoke the mocked method, which is not the same as calling the real method.
To be fair more time could be invested in building out a more robust Mock like the Python based Moto, which is a sophisticated mocking framework that creates robust mocks for tests.
However, then there is the additional burden of maintaining the mocks, which isn't advancing the code base any, and the Mocks are bound to have bugs as well as the main codebase.
So, in other words Mocks add more work and provide only a little bit more peace of mind.
When not to Mock: Create Real Instance of Dependency
Now, let's consider the reader's original question of how to test a Database. Although, it could be mocked and some readers might argue that it should.
I am going to suggest creating the database during the execution of the tests via Docker containers.
However, I caution to only consider doing so when (1) the project can afford longer build times (1-x amount of minutes potentially added, dependent on the test(s)) and (2) there is an easy way to do so.
But, the peace of mind of knowing that the code is actually being tested against a real database is well worth it in my opinion for the 1-x amount of minutes added to the pipeline.
Again, my reasoning for using real instances over mocks is that mocks are only a simulation of the instance and therefore can never be as good as the real instance.
Put another way, although driving a digital car with steering and pedals can simulate driving it can never (at this point in time) simulate all variables of actually driving
With that in mind let's create the test for a database.
Suppose, we are building an application that is to take some EC2 instance metadata and store it into a database for later use by the security or compliance team.
Then the code might look like this:
1. Define the Data Structure
We'll only store a small piece of the EC2 metadata available. So, create an Infrastructure type with ImageId, InstanceId, InstanceType, LaunchTime, MonitoringState, and PlacementZone fields.
Next, create a few sample data points (can call the AWS API through the AWS CLI to get the data or view the AWS console)
2. Set up Test with Docker Containers
Then, initialize the dockertest library to create a new docker container pool for use.
Note: If testing locally on your computer it will require you to be signed into dockerhub as it will be making calls to it to get the image(s) if they aren't all ready on your local computer.
This also means that it will do the same within your pipeline so make sure that you have the login credentials saved as environment variables and have a login to docker step.
3. Create a database and return a connection to the database to be used
To make it easier to write multiple tests that use the Database, we'll create a function that creates a database and returns a database connection.
Note, that this implies that for the testing of the functions that use the database that there will be a database created each time within a docker container.
It is important to keep the test databases separate in my opinion to avoid the scenarios of functions executing out of order (t.Parallel()) and the database being in the wrong state for the test.
4. Test the DB
Now, we can create the test for the database.
Notice, calling the db.Save() method is the same as calling the Gorm function, so we are actually interacting with a real database in this test. At a glance, it may seem like a lot of work and not very useful for such a simple test.
However, it opens the door to more complex business logic tests that involve database interaction. Additionally, it allows for a way to test the database itself.
Imagine, you wanted to test the performance of MySQL vs PostGres, now all that would be required is to swap out the databases in the db function or split it into two functions gormDB and postgresDB (what I'd do).
If mocking were instead used then it would mean the creation of a whole new set of Mocked objects (more work on top of testing / writing business code). And again, the Mocks still wouldn't be able to supply useful information, such as performance.
I'm providing the full code in case there are any problems with following along.
It is important to know when to mock and when not to mock. Additionally, to know the pros and cons for mocking and for creating the real instance.