Hi, community, I decided to start learning rust lang, and started to experiment with my pet project. This is Lineage 2 game server emulator in rust, it goes well but I have a problem with writing unit tests.
Background.
The application consists of Login and Game TCP servers which receives and sends TCP packets (to the game client or each other). I have an abstraction in my app (a trait) which is HandleablePacket and I have actually 2 traits in login and game crates because method "handle" must be implemented differently in each of this crates depending who is receiver and who is sender.
Here is an example of Auth packet which implements HandlablePacket trait in game crate.
I want to be able to test "handle" method in that packet, currently unit test is written by sending bytes over tokio duplex which is not ideal because I don't want to test packet reading and sending logic but only "handle" call. The problem is that it expects concrete type to be passed in "ClientHandler" and inside "handle" it calls client_handler.send_packet - which is calling socket write.
Is there a way to "mock" send_packet or pass different implementation of ClientHandler to the "handle" method e.g. swap associated type? Or i need completely restructure my application?
Thanks
Some people have used "sans-io" approach for network services where the logic of the service doesn't handle I/O itself, and instead is just a state machine taking bytes in and spitting states out. This way it can be easily controlled from the tests, without involving the whole network stack.
However, if you don't want to rewrite everything in a new style, then testing over local network isn't too bad. localhost
is really fast, and you could use unix domain sockets if you'd rather worry about temporary files than ports. The server can be spawned on a separate thread from the test driving it. If every test needs some common work like logging in, you could add a dedicated method for doing that that you can call directly when instantiating the server.
I actually used tokio duplex (AsyncRead / AsyncWrite) it helped abstract from sockets. But the problem is not in networking but in the way how I can test concrete implementation, in this case AuthPacket expects Concrete type to be passed in "ClientHandler", why? because auth packet contains a logic to control what to do with a client if "handle" is called.
So the question is more about swapping out associated types (is suppose it's just a syntax sugar for generics) in implementation with something else
Do you mean doing this?
impl MyHandler for MyLogic {
type Packet = ActualPacket; // <-- test using InjectedPacket
// ...
}
Associated types are like an 'output' type of a trait definition. If you want the caller to be able to control the type used, it needs to be an input, a generic argument on the trait, or the type:
impl MyHandler<T> for MyLogic
- or
impl MyHandler for MyLogic<T>