WASP - Testing User Journeys
Let's explore a more complex scenario where a user needs to authenticate first before performing an action. Additionally, we will introduce a slightly more advanced load profile:
- 1 user for the first 30 seconds
- 2 users for the next 30 seconds
- 3 users for the final 30 seconds
Since this is a "user journey," we will use a VirtualUser
implementation to represent a user.
Defining the Virtual User
First, let's define the VirtualUser
struct:
type VirtualUser struct {
*wasp.VUControl
target string
Data []string
rateLimit int
rl ratelimit.Limiter
client *resty.Client
}
Here, we’ve added a rate limiter to the struct, which will help limit the number of requests per second. This is useful to prevent overloading the server, especially in this test scenario where the requests are simple and fast. Without a limiter, even a small number of Virtual Users (VUs) could result in a very high RPS.
note
Since VirtualUser
does not inherently limit RPS (it depends on the server's processing speed), you should implement a rate-limiting mechanism if needed.
For brevity, we'll skip the implementation of the Clone()
and Teardown()
functions, as they are similar to previous examples. Additionally, no Setup()
is required because we are using HTTP.
Implementing Requests
User Authentication
// requestOne represents user login
func (m *VirtualUser) requestOne(l *wasp.Generator) {
var result map[string]interface{}
r, err := m.client.R().
SetResult(&result).
Get(m.target)
if err != nil {
l.Responses.Err(r, GroupAuth, err)
return
}
l.Responses.OK(r, GroupAuth)
}
Authenticated Action (e.g., Balance Check)
// represents authenticated user action
func (m *VirtualUser) requestTwo(l *wasp.Generator) {
var result map[string]interface{}
r, err := m.client.R().
SetResult(&result).
Get(m.target)
if err != nil {
l.Responses.Err(r, GroupUser, err)
return
}
l.Responses.OK(r, GroupUser)
}
Combining the Requests
func (m *VirtualUser) Call(l *wasp.Generator) {
m.rl.Take() // apply rate limiting
m.requestOne(l)
m.requestTwo(l)
}
Writing the Test
Now, let’s write the test. Pay attention to how the three phases of the load profile are defined under Schedule
:
func TestScenario(t *testing.T) {
srv := wasp.NewHTTPMockServer(nil)
srv.Run()
_, err := wasp.NewProfile().
Add(wasp.NewGenerator(&wasp.Config{
T: t,
LoadType: wasp.VU,
VU: NewExampleScenario(srv.URL()),
Schedule: wasp.Combine(
wasp.Plain(1, 30*time.Second),
wasp.Plain(2, 30*time.Second),
wasp.Plain(3, 30*time.Second),
),
LokiConfig: wasp.NewEnvLokiConfig(),
})).Run(true)
require.NoError(t, err)
}
Load Profile
We generate load in three phases:
- 1 user for the first 30 seconds
- 2 users for the next 30 seconds
- 3 users for the final 30 seconds
Conclusion
And that's it! We’ve created a test that simulates a user journey with authentication and an action requiring authentication, while varying the load during execution. You can find the full example code here.
But what if you wanted to combine multiple load generators—or even mix a Gun
with a VirtualUser
? Could you do that? Find out in the next chapter.