RGS / Testing

How Do You Test an RGS Without a Live Operator?

Using a simulated operator to test game launches, wallet transactions and the things that go wrong

A game launches. The player places a bet. The RGS sends a debit request. The wallet accepts it. The game resolves, the win is credited, and everybody agrees about the player's balance.

Excellent.

You have established that your RGS works when everything else works too. Unfortunately, production has a larger imagination.

The operator you don't have yet

An RGS rarely exists by itself. It has to launch games for an operator, validate sessions, communicate with a wallet, move money, and keep its own view of rounds and transactions consistent with systems outside its control.

Eventually, the best test of an operator integration is the operator itself. But that isn't always available while the RGS is being developed — an operator may still be implementing its side of the integration, access to a test environment may be limited, credentials may not exist yet, or you may simply want to develop and test without making another engineering team part of your feedback loop.

The obvious answer is to mock the operator.

At its simplest, that might mean a few endpoints that accept the expected requests and return the expected responses. That's enough to get surprisingly far. It's also enough to become surprisingly confident.

A happy-path wallet isn't much of an adversary

The interesting behaviour begins when a request and its response stop being the same thing.

Suppose the RGS sends a debit for 10.00. The operator processes it successfully, reducing the player's balance by 10.00. But the response never reaches the RGS.

From the operator's point of view, the transaction succeeded. From the RGS's point of view, it didn't receive an answer.

Sending the debit again might be correct. It might also charge the player twice. Doing nothing might avoid a duplicate debit. It might also leave the RGS believing money was never taken.

No response does not mean no transaction.

Game — Bet placed
RGS → Operator
DEBIT 10.00
sent
Simulated Operator / Wallet
DEBIT SUCCEEDED
response lost ✕
RGS
NO RESPONSE / UNKNOWN
What should the RGS do next?

Now add latency. Rejected transactions. A credit that fails twice before succeeding. A session that expires. A rollback. A connection that disappears halfway through a sequence. These are the kinds of conditions a system handling transactions has to expect eventually, and they're awkward things to test by waiting for them to happen.

Simulating more than one operator

There's another problem with a mock that behaves perfectly: it can quietly teach the RGS that there's only one way for an operator to behave, when in practice operators expose different APIs, different authentication, different wallet calls, different game-launch flows and parameters, different error responses. Even where two APIs perform essentially the same job, their assumptions and transaction semantics may not be identical.

Testing against one simulated interface proves something useful, but limited.

For our own RGS development, we found it more useful to simulate several operators, with separate entry points and different APIs. A game can be launched against one simulated operator and then another, exercising the relevant integration rather than a single universal mock interface designed to agree with everything.

This matters as integrations accumulate. Operator-specific behaviour should ideally stay at the integration boundary rather than gradually finding its way into the rest of the RGS — and testing against different interfaces is one way of finding out whether it has.

Sometimes you need the operator to misbehave on purpose

A real operator environment remains essential for real integration testing. But a simulated operator has one considerable advantage over the real thing: you control it.

You can tell a wallet call to take too long. You can make a particular operation fail. You can drop a response. You can arrange for the first two credit attempts to fail and the third to succeed. Then you can run exactly the same scenario again. And again.

A real operator is usually trying quite hard to remain available, which makes it surprisingly unhelpful when what you need is one that fails on command.

This is why our mock environment gradually became more than a collection of endpoints. We added configurable behaviour and failure conditions, because reproducing a bad transaction reliably is much more useful than knowing one happened last Tuesday.

Test the game, not just the calls

API tests are necessary, but the player doesn't experience an API — they launch a game.

So our simulated environment also lets us enter as a player, launch games through the RGS, and actually play them. Behind that sits an admin side where the operator environments, transactions, and test behaviour can be controlled and observed.

That closes an important gap. A wallet request may look correct in isolation while the complete journey isn't. A session problem may only become apparent during play. A recovery mechanism may leave the backend technically consistent but the game unable to continue sensibly.

Following the path from player to game to RGS to operator, and back again, tests the system that actually exists rather than just the individual interfaces that compose it.

A simulator has limits

Eventually, you still have to integrate with the real thing. Real systems contain behaviours your simulator didn't anticipate, and production always finds something the test environment didn't cover.

That's fine — it just means being clear about what simulation is actually for. A simulated operator gives an RGS team a controlled environment to develop integrations before external systems are ready, exercise different operator APIs, reproduce transaction failures, test recovery behaviour, and run the same awkward scenario as many times as necessary.

Then the real operator tells you what you failed to imagine. Which is generally plenty.

Start where others finish.