Stoplight Prism

Mock Testing with Stoplight Prism



Testing plays a crucial role in the software development process. Identifying issues before deployment rather than in production provides a significant advantage for code quality and API reliability.

However, there are instances where testing third-party services becomes either complex or unnecessary. This is where mocking becomes invaluable. Mocking allows you to focus on testing your core business logic without poking into the sophistication of other service behaviours.

What is Prism?

Prism is an open-source HTTP mock server that can emulate your API's behavior as if you already built it. Mock HTTP servers are generated from your OpenAPI v2/v3 documents.

In simple terms, Prism enables you to deploy an HTTP server within a Docker container, responding to predefined API calls based on specifications outlined in a YAML file. This streamlined approach significantly simplifies testing, allowing you to focus on your core business logic without unnecessary complexities.


Let's get hands-on and build something while ensuring it's thoroughly tested. Imagine we have a service that a customer calls to create a user. We can explore a couple of potential endpoints:

type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
func main() {
// Create a new Gin router
router := gin.Default()
// Define routes
router.GET("/users", getAllUsers)
router.POST("/users", createUser)
router.GET("/users/:userId", getUserByID)
router.PUT("/users/:userId", updateUserByID)
router.DELETE("/users/:userId", deleteUserByID)
// Start the HTTP server on port 8080
err := router.Run(":8080")
if err != nil {
panic("Error starting the server: " + err.Error())

These endpoints are self-explanatory, constituting a straightforward REST API. Now, let's dive into the implementation of the createUser function.

// Handler function to create a new user
func createUser(c *gin.Context) {
// Decode the JSON request body into a User struct
var newUser User
if err := c.ShouldBindJSON(&newUser); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
// Validate user data
if newUser.Name == "" || newUser.Email == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user data"})
// Assign a unique ID and add the user to the list
newUser.ID = len(users) + 1
users = append(users, newUser)
err := superImportantService.SendUserDetails(newUser)
if err != nil {
c.JSON(http.StatusInternalServerError, "Error connecting to Super Important Service")
// Respond with the created user
c.JSON(http.StatusCreated, newUser)

This function is quite straightforward. We receive user data, validate that it's not empty, save the user to our database, send the user to superImportantService, and return a 201 status code.

Hold on a moment! What's this superImportantService?

Well, it's our third-party service where we need to store the user for a super important purpose.

type SuperImportantService struct {
EndpointURL string
// NewSuperImportantService creates a new instance of SuperImportantService
func NewSuperImportantService() *SuperImportantService {
endpointURL := os.Getenv("SUPER_IMPORTANT_SERVICE_URL") // ""
return &SuperImportantService{EndpointURL: endpointURL}

Overall, the architecture looks like this:


The question arises when we want to test our service independently of the Super Important Service. Since it's not accessible from our local environment, tests will consistently fail.

Mocking Service

Here comes Prism! We can effortlessly create an entire Mock API Server using the OpenAPI specification. It's incredibly simple yet powerful.

While I won't provide the entire specification, here's an example of what it can look like:

openapi: 3.0.0
title: User API
version: 1.0.0
summary: Create a new user
required: true
type: object
type: string
type: string
- name
- email
description: User created successfully
type: object
type: integer
type: string
type: string
id: 3
name: New User

Let's take a look at it. We have information about our API, including the version and title. Following that are the paths, specifically the POST request for /users. The API requires a name and email and returns a 201 response code. While in real-life scenarios, we might encounter various response codes, for our small example project this simplification is enough.

Now, let's run it!

npm install -g @stoplight/prism-cli
docker run --init --rm -v $(pwd):/tmp -p 4010:4010 stoplight/prism:5.5.1 mock -h "/tmp/user-api-openapi.yaml"

Yes, it's that simple. If you've done everything correctly, you'll see a list of possible API endpoints and the URL of a Prism server. Now, you can set it up in your environment variables and test your application. You can even take it a step further and run test containers from Go tests! However, that's a topic for next time.

[6:48:38 AM][CLI] … awaiting Starting Prism…
[6:48:40 AM][CLI] ℹ info GET
[6:48:40 AM][CLI] ℹ info POST
[6:48:40 AM][CLI] ℹ info GET
[6:48:40 AM][CLI] ℹ info PUT
[6:48:40 AM][CLI] ℹ info DELETE
[6:48:40 AM][CLI] ▶ start Prism is listening on

Here is a potential test function:

func TestCreateUser(t *testing.T) {
tests := []struct {
name string
user User
expCode int
// TODO: define test cases
// Iterate through test cases
for _, tt := range tests {
t.Run(, func(t *testing.T) {
// TODO: run test cases


In summary, Prism simplifies API testing by offering a smooth experience. It enables you to refine your code without the complexities of third-party services. Whether you're configuring Prism for local tests or exploring your function's behaviour without external dependencies, it proves to be a valuable addition to your testing toolkit. Embrace Prism for smoother coding experiences!