Mocking
Testing your application in isolation
Encore comes with built-in support for mocking out APIs and services, which makes it easier to test your application in isolation.
Mocking Endpoints
Let's say you have an endpoint that calls an external API in our products
service:
//encore:api private
func GetPrice(ctx context.Context, p *PriceParams) (*PriceResponse, error) {
// Call external API to get the price
}
When testing this function, you don't want to call the real external API since that would be slow and cause your tests to fail if the API is down. Instead, you want to mock out the API call and return a fake response.
In Encore, you can do this by adding a mock implementation of the endpoint using the et.MockEndpoint
function inside your test:
package shoppingcart
import (
"context"
"testing"
"encore.dev/et" // Encore's test support package
"your_app/products"
)
func Test_Something(t *testing.T) {
t.Parallel() // Run this test in parallel with other tests without the mock implementation interfering
// Create a mock implementation of pricing API which will only impact this test and any sub-tests
et.MockEndpoint(products.GetPrice, func(ctx context.Context, p *products.PriceParams) (*products.PriceResponse, error) {
return &products.PriceResponse{Price: 100}, nil
})
// ... the rest of your test code here ...
}
When any code within the test, or any sub-test calls the GetPrice
API, the mock implementation will be called instead.
The mock will not impact any other tests running in parallel. The function you pass to et.MockEndpoint
must have the same
signature as the real endpoint.
If you want to mock out the API for all tests in the package, you can add the mock implementation to the TestMain
function:
package shoppingcart
import (
"context"
"os"
"testing"
"encore.dev/et"
"your_app/products"
)
func TestMain(m *testing.M) {
// Create a mock implementation of pricing API which will impact all tests within this package
et.MockEndpoint(products.GetPrice, func(ctx context.Context, p *products.PriceParams) (*products.PriceResponse, error) {
return &products.PriceResponse{Price: 100}, nil
})
// Now run the tests
os.Exit(m.Run())
}
Mocks can be changed at any time, including removing them by setting the mock implementation to nil
.
Mocking services
As well as mocking individual APIs, you can also mock entire services. This can be useful if you want to inject a different
set of dependencies into your service for testing, or a service that your code depends on. This can be done using the
et.MockService
function:
package shoppingcart
import (
"context"
"testing"
"encore.dev/et" // Encore's test support package
"your_app/products"
)
func Test_Something(t *testing.T) {
t.Parallel() // Run this test in parallel with other tests without the mock implementation interfering
// Create a instance of the products service which will only impact this test and any sub-tests
et.MockService("products", &products.Service{
SomeField: "a testing value",
})
// ... the rest of your test code here ...
}
When any code within the test, or any sub-test calls the products
service, the mock implementation will be called instead.
Unlike et.MockEndpoint
, the mock implementation does not need to have the same signature, and can be any object. The only requirement
is that any of the services APIs that are called during the test must be implemented by as a receiver method on the mock object.
(This also includes APIs that are defined as package level functions in the service, and are not necessarily defined as receiver methods
on that services struct).
To help with compile time safety on service mocking, for every service Encore will automatically generate an Interface
interface
which contains all the APIs defined in the service. This interface can be passed as a generic argument to et.MockService
to ensure
that the mock object implements all the APIs defined in the service:
type myMockObject struct{}
func (m *myMockObject) GetPrice(ctx context.Context, p *products.PriceParams) (*products.PriceResponse, error) {
return &products.PriceResponse{Price: 100}, nil
}
func Test_Something(t *testing.T) {
t.Parallel() // Run this test in parallel with other tests without the mock implementation interfering
// This will cause a compile time error if myMockObject does not implement all the APIs defined in the products service
et.MockService[products.Interface]("products", &myMockObject{})
}
Automatic generation of mock objects
Thanks to the generated Interface
interface, it's possible to automatically generate mock objects for your services using
either Mockery or GoMock.