Stubbing and Verifying: My Journey to Smarter Testing ??
Let’s talk about stubbing and verifying - two critical tools that transformed how I approach testing. Early in my career, testing was often frustrating, slow, and full of surprises, most of them bad. I used to think testing meant running my app against real systems and crossing my fingers that everything worked. Spoiler: it rarely did.
Here’s the thing: testing doesn’t have to be painful. Let me share how embracing stubbing and verifying - and tools like Keploy - helped me with testing.
The Problem: Testing Dependencies is Messy
Picture this: you're testing a piece of code that relies on an external API, a database, or some other dependency. Ideally, your test should just focus on your code. But when you call that dependency:
Testing in this way is like trying to build a sandcastle during high tide - frustrating and doomed to fail. Relying on real systems for testing is just not practical.
The First Breakthrough: Stubbing ???
Here’s where stubbing comes in. Instead of depending on real systems, I started creating controlled responses - fake data, if you will. A stub is like an actor playing a role: it gives a predictable performance no matter what chaos is happening offstage.
A Simple Analogy
Say you’re testing a food delivery app. Instead of calling the real kitchen to check the pizza status, you create a stub that always responds, “Your pizza is on the way!” That’s stubbing in a nutshell: controlling the outcome to test your app without relying on real-world systems.
In code, stubbing often looks something like this:
// Define a struct for the response
type Response struct {
Status int `json:"status"`
Message string `json:"message"`
}
// Create a mock function that returns the response
fakeAPI := func() func() (*Response, error) {
return func() (*Response, error) {
return &Response{
Status: 200,
Message: "Pizza is on the way!",
}, nil
}
}()
This approach keeps tests fast, reliable, and independent of external factors.
Meet Keploy: Smarter Stubbing at Scale
While manual stubs work for small tests, scaling them across a complex application can be tricky. That’s where Keploy comes in. It’s a powerful open-source tool that simplifies creating data stubs. What makes Keploy stand out is its ability to automatically record real-world interactions (e.g., API calls or database queries) and convert them into reusable test cases or stubs.
How Keploy Works
This eliminates the need to write stubs manually, saving time and reducing errors.
Adding Confidence: Verification ???♂?
While stubbing ensures your code doesn’t depend on flaky systems, verification ensures your code interacts with those systems correctly. This is where you check things like:
For example:
func TestFakeAPIWithTestify(t *testing.T) {
assert := assert.New(t)
mock := new(MockAPI)
mock.On("Call", "extra cheese").Return(&Response{
Status: 200,
Message: "Pizza is on the way!",
}, nil)
// Call the API
mock.Call("extra cheese")
mock.AssertCalled(t, "Call", "extra cheese")
mock.AssertNumberOfCalls(t, "Call", 1)
}
Verification catches those subtle bugs that can sneak in when your app doesn’t behave as expected. It’s the difference between assuming your app works and proving it does.
A Practical Example: Testing a Weather App
Let’s say you’re building a weather app that fetches data from an external API. With mocking/stubbing, your test might look something like this:
package main
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
// mockWeatherResponse creates a mock weather response
func mockWeatherResponse() WeatherResponse {
return WeatherResponse{
Latitude: 52.52,
Longitude: 13.41,
Timezone: "America/Denver",
Current: struct {
Temperature2m float64 `json:"temperature_2m"`
}{
Temperature2m: 20.5,
},
Hourly: struct {
Time []string `json:"time"`
Temperature2m []float64 `json:"temperature_2m"`
}{
Time: []string{"2025-01-07T00:00", "2025-01-07T01:00"},
Temperature2m: []float64{20.5, 19.8},
},
Daily: struct {
Time []string `json:"time"`
WeatherCode []int `json:"weather_code"`
Sunrise []string `json:"sunrise"`
Sunset []string `json:"sunset"`
}{
Time: []string{"2025-01-07"},
WeatherCode: []int{0},
Sunrise: []string{"2025-01-07T07:15"},
Sunset: []string{"2025-01-07T16:45"},
},
}
}
// MockOpenMeteoServer creates a test server that mimics the OpenMeteo API
func MockOpenMeteoServer() *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mockResponse := mockWeatherResponse()
json.NewEncoder(w).Encode(mockResponse)
}))
}
func TestAPIEndpoint(t *testing.T) {
assert := assert.New(t)
// Create a request to our API endpoint
req := httptest.NewRequest("GET", "/api/weather", nil)
w := httptest.NewRecorder()
// Call the handler
weatherHandler(w, req)
// Check response
resp := w.Result()
assert.Equal(http.StatusOK, resp.StatusCode)
assert.Equal("application/json", resp.Header.Get("Content-Type"))
// Parse response body
var weather WeatherResponse
err := json.NewDecoder(resp.Body).Decode(&weather)
assert.NoError(err)
// Verify response data
assert.Equal(52.52, weather.Latitude)
assert.Equal(13.419998, weather.Longitude)
assert.Equal("America/Denver", weather.Timezone)
assert.Equal(6.3, weather.Current.Temperature2m)
}
func TestHTMLEndpoint(t *testing.T) {
assert := assert.New(t)
// Create a request to our HTML endpoint
req := httptest.NewRequest("GET", "/weather", nil)
w := httptest.NewRecorder()
// Call the handler
weatherHTMLHandler(w, req)
// Check response
resp := w.Result()
assert.Equal(http.StatusOK, resp.StatusCode)
assert.Equal("text/html", resp.Header.Get("Content-Type"))
// Read response body
body, err := io.ReadAll(resp.Body)
assert.NoError(err)
// Check if HTML contains expected elements
htmlContent := string(body)
assert.True(strings.Contains(htmlContent, "<title>Weather Forecast</title>"))
assert.True(strings.Contains(htmlContent, "Current Weather"))
assert.True(strings.Contains(htmlContent, "Daily Forecast"))
assert.True(strings.Contains(htmlContent, "Hourly Forecast"))
}
func TestGetWeatherDescription(t *testing.T) {
assert := assert.New(t)
testCases := []struct {
code int
expected string
}{
{0, "Clear sky"},
{1, "Mainly clear"},
{95, "Thunderstorm"},
{999, "Unknown weather condition"},
}
for _, tc := range testCases {
result := getWeatherDescription(tc.code)
assert.Equal(tc.expected, result)
}
}
Looks fine, right? But what happens if:
领英推荐
Instead, with Keploy, you can capture the API response once and reuse it:
Keploy handles the heavy lifting, giving you a reliable and scalable way to manage test data without manually creating or maintaining stubs. This is show our mocks would look like: -
The Payoff: Faster, Better Testing
Once I embraced stubbing and verifying (with a help from Keploy), everything changed:
Pro Tips for Effective Stubbing and Verifying
Now that you see the benefits, let’s talk about how to use stubs and verifications effectively:
Why It Matters
Testing is all about confidence - confidence that your code works, that it handles edge cases, and that changes won’t break anything important. Stubbing and verifying gave me that confidence. Tools like Keploy took it a step further, making testing faster, easier, and more scalable.
These tools aren’t just for big projects or senior devs. Whether you’re working on a hobby project or a production app, stubbing and verifying (with automation) can save you time, frustration, and headaches.
Conclusion
Testing with stubbing and verifying isn’t about cutting corners—it’s about being smart. It lets you focus on your code and isolate problems without worrying about the outside world. Once you get the hang of it, you’ll wonder how you ever managed without it.
So, next time you’re testing something, remember:
Testing doesn’t have to be frustrating. With the right tools, it can even be (dare I say) fun. ??
FAQs
What’s the difference between stubbing and mocking?
How does Keploy differ from traditional stubbing tools?
Keploy automates the creation of stubs by capturing real-world interactions (e.g., API calls or database queries) and converting them into reusable test cases. Unlike manual stubbing:
When should I use stubbing vs. real-world testing?
What’s the best way to combine stubbing and verifying in tests?
To achieve robust testing:
This article was originally published on Keploy.io