Version: v0.9.0

Tests

No matter how good we are, every new piece of code we add could be a new bug.

Rust differentiates three types of testing practices: unit testing, doc testing and integration testing. As the last two are meant for libraries, we're going to focus in the first one: Unit Testing.

Configuring Tests

There's nothing special we have to do to execute our unit tests. However, we're probably going to need to mock some of our app components in order to make them work. For this purpose Kalgan supports the feature test which provides a collection of mocking tools.

This feature must be added in the [dev-dependencies] section of our Cargo.toml.

Notice that since Rust 1.51, Cargo supports a new feature resolver which can be activated with resolver = "2" in our Cargo.toml. This new resolver assures that all features enabled on dev-dependencies will not be unified when those same dependencies are used as a normal dependency, unless those dev-dependencies are currently being built.

Therefore, our Cargo.toml should look as follows:

[package]
edition = "2021"
resolver = "2"
...

[dev-dependencies]
kalgan = { version = "0.9.0", features = ["test"] }

Be aware that test feature should not be requested in [dependencies] as it's thought to be used just for testing and not for development or building purpose.

Mock Objects

In software testing mock objects are simulated components that reproduce the behavior of the real ones. When executing unit tests we need to mock the components used by the target function, otherwise the tests would fail. For example, when testing our controllers we're going to need to mock the Request and Response objects.

Kalgan provides a collection of functions to create these components and traits that the break the visibility restrictions of some of other Kalgan components.

Mocking app configuration

Routes, translation messages and settings parameters don't exist when executing unit tests. In order to create them Kalgan provides the following functions:

kalgan::mock_settings Creates the given settings parameters.
kalgan::mock_routes Creates the route parameters for routing system. kalgan::mock_settings must be launched first.
kalgan::mock_i18n Creates the message parameters for translation system. kalgan::mock_settings must be launched first.

Mocking Request object

As we have already seen, Request fields are private and can't be changed. Trator provides kalgan::http::request::Mock trait which contains all setter methods.

Find the definition of this trait in the code below:

 pub trait Mock<'a> {
    fn mock() -> Self;
    fn mock_set_method(self, method: String) -> Self;
    fn mock_set_uri(self, uri: String) -> Self;
    fn mock_set_protocol(self, protocol: String) -> Self;
    fn mock_set_cookies(self, cookies: HashMap<String, String>) -> Self;
    fn mock_set_host(self, host: String) -> Self;
    fn mock_set_user_agent(self, user_agent: String) -> Self;
    fn mock_set_input(self, input: HashMap<String, String>) -> Self;
    fn mock_set_referer(self, referer: String) -> Self;
    fn mock_set_files(self, files: HashMap<String, File<'a>>) -> Self;
    fn mock_set_raw(self, raw: String) -> Self;
} 

Mocking Response object

As with Request object, Response fields are private and can't be read. Trator provides kalgan::http::response::Mock trait which contains all getter methods.

Find the definition of this trait in the code below:

pub trait Mock {
    fn mock_get_status(self) -> String;
    fn mock_get_content_type(self) -> String;
    fn mock_get_location(self) -> String;
    fn mock_get_cookies(self) -> Vec;
    fn mock_get_content(self) -> String;
    fn mock_get_content_length(self) -> String;
}

Creating Tests

Let's see how we can create a couple of tests for our controller.

Given the following controller::foo_controller::foobar:

use kalgan::http::request::Request;
use kalgan::http::response::Response;

pub(crate) fn foobar(request: &Request) -> Response {
    render!("foobar.html")
}
...

We're going to create two test cases: test_foobar_status and test_foobar_title. The former is for testing the response status code and the latter to test the <title> HTML block of the template. Notice we have to import traits kalgan::http::request::Mock and kalgan::http::response::Mock to break the visiblity of Request and Response objects, respectively.

Following Rust guidelines we must add our test block at the end of the file:

...
#[cfg(test)]
mod tests {
    use super::*;
    use kalgan::http::request::Mock as MockRequest;
    use kalgan::http::response::Mock as MockResponse;

    #[test]
    fn test_foobar_status() {
        kalgan::mock_settings("config/app");
        let request = Request::mock();
        let response = foobar(&request);
        assert_eq!(response.mock_get_status(), "HTTP/1.1 200 OK");
    }
    #[test]
    fn test_foobar_title() {
        kalgan::mock_settings("config/app");
        kalgan::mock_i18n();
        let request = Request::mock();
        let response = foobar(&request);
        assert!(response.mock_get_content().contains("..."));
    }
}

Finally we just have to execute the tests:

cargo test