testingrustrust-cargo

How to test web REST server


I'm currently writing a REST web server with Rust.

I'm using "reqwest" to test the REST API. To get these tests running, I need to have an instance of the server running.

If I launch it before running "cargo test", cargo fails as it can overwrite the binary (on Windows)

So, I've tried to:

  1. run the server with ("cargo run") before running the tests
  2. stop the server after all test are run.

For 1), I've found several solution (test_env_helpers, Once, ...) But for 2), I haven't found a solution. I've tried:

Any suggestion?


Solution

  • One solution is to use an integration test. During cargo test, they're compiled separately from and have access to the server binary. You're responsible for figuring out the path to the binary, though. On supported operating systems, libc::atexit allows killing the server after tests have concluded.

    // tests/test_server.rs
    use libc::atexit;
    use std::io::{BufRead, BufReader};
    use std::process::{Child, Command, Stdio};
    use std::sync::Mutex;
    
    pub fn ensure_server_started() {
        static STARTED: Mutex<Option<Child>> = Mutex::new(None);
    
        let mut started = STARTED.lock().unwrap();
        if started.is_some() {
            return;
        }
        let mut path = std::env::current_exe().unwrap();
        assert!(path.pop());
        if path.ends_with("deps") {
            assert!(path.pop());
        }
    
        // Note: Cargo automatically builds this binary for integration tests.
        path.push(format!(
            "{}{}",
            env!("CARGO_PKG_NAME"),
            std::env::consts::EXE_SUFFIX
        ));
    
        let mut cmd = Command::new(path);
        cmd.env_clear();
        cmd.stdin(Stdio::piped());
        cmd.stdout(Stdio::piped());
        cmd.stderr(Stdio::piped());
        // TODO: cmd.args([]);
    
        let mut server = cmd.spawn().unwrap();
    
        let stdout = server.stdout.take().unwrap();
        let reader = BufReader::new(stdout);
        let mut lines = reader.lines();
    
        let mut listening = false;
        while let Some(Ok(line)) = lines.next() {
            // TODO: Server needs to print "listening" to stdout
            if line.contains("listening") {
                listening = true;
                break;
            }
        }
        assert!(listening);
    
        extern "C" fn kill() {
            STARTED.lock().unwrap().as_mut().unwrap().kill().unwrap();
        }
    
        unsafe { atexit(kill) };
    
        *started = Some(server);
    }
    
    #[test]
    pub fn test_server() {
        // Beginning of every test.
        ensure_server_started();
    
        // TODO: Test
    }