dockertest_server/
test.rs

1/// Contains types for creating tests
2use dockertest::{Composition, DockerOperations, DockerTest};
3use futures::Future;
4use type_map::concurrent::TypeMap;
5
6use crate::server::{Config, Server};
7
8/// A single test which brings up one or more [Servers][Server].
9///
10/// A [Test][crate::test::Test] receives [Configs][Config] via `register` which
11/// determines the servers that will be brought up when the test is executed.
12/// The `run` method is used to bring up the [Servers][Server] and then run the
13/// given test body.
14///
15/// The body of a test is determined by an async closure passed to the `run`
16/// method. The closure will receive a [TestInstance] which can be used to
17/// access the [Servers][Server] that were created when the test was setup.
18pub struct Test {
19    pub configs: TypeMap,
20    pub compositions: Vec<Composition>,
21}
22
23impl Test {
24    /// Creates an empty [Test].
25    pub fn new() -> Self {
26        Test {
27            configs: TypeMap::new(),
28            compositions: Vec::new(),
29        }
30    }
31
32    /// Registers a [Config] with this test.
33    ///
34    /// A [Test] can be configured with any number of [Configs][Config] for
35    /// determining which [Servers][Server] are brought up in a test. Each
36    /// [Config] passed will have it's respective [Server] created before the
37    /// test body is ran.
38    pub fn register(&mut self, config: impl Config + 'static) {
39        self.configs.insert(config.clone());
40        self.compositions.push(config.into_composition());
41    }
42
43    /// Brings up the [Servers][Server] registered with this test and then
44    /// runs the given test body.
45    ///
46    /// The test body receives a [TestInstance] which can be used for accessing
47    /// any configured [Servers][Server] through the `server` method.
48    /// The test body is guaranteed to not execute until all configured
49    /// [Servers][Server] are verified to be running and available. The scope of
50    /// the test body determines the life of the [Servers][Server]: they are
51    /// created before the closure is run and destroyed after the closure exits.
52    pub fn run<T, F>(self, fun: T)
53    where
54        T: FnOnce(TestInstance) -> F + Send + 'static,
55        F: Future<Output = ()> + Send + 'static,
56    {
57        let mut test = DockerTest::new();
58        for comp in self.compositions {
59            test.add_composition(comp)
60        }
61
62        let configs = self.configs;
63        test.run(|ops| async move {
64            let instance = TestInstance::new(configs, ops);
65            (fun)(instance).await;
66        });
67    }
68}
69
70impl Default for Test {
71    fn default() -> Self {
72        Self::new()
73    }
74}
75
76/// Represents a running instance of a [Test].
77///
78/// Internally, this type keeps track of all [Configs][Config] that were
79/// registered with a [Test] using a [TypeMap]. A [Server] can be fetched by
80/// calling the `server` method with the desired type that implements [Server].
81/// See the associated method documentation for more details.
82pub struct TestInstance {
83    pub configs: TypeMap,
84    pub op: DockerOperations,
85}
86
87impl TestInstance {
88    /// Returns a new [TestInstance].
89    pub fn new(configs: TypeMap, op: DockerOperations) -> Self {
90        TestInstance { configs, op }
91    }
92
93    /// Returns an instance of the requested type that implements [Server].
94    ///
95    /// Internally a [TestInstance] has a [TypeMap] which contains all of the
96    /// [Configs][Config] that were registered with a given [Test]. Since each
97    /// [Config] has a one-to-one relationship with a [Server], an instance of a
98    /// [Server] can be created by simply having a copy of it's associated
99    /// [Config]. This method performs that mapping for you, looking for a
100    /// [Config] of the type specified by [Server::Config] in the type map and
101    /// then generating and returning a new instance of the [Server] using the
102    /// [Config] and runtime data generated when the underlying container was
103    /// brought up.
104    ///
105    /// Note that it then follows this method will fail if the passed [Server]
106    /// type did not have it's associated [Config] registered when the [Test]
107    /// was created.
108    pub fn server<S: Server>(&self) -> S {
109        let config = self.configs.get::<S::Config>().unwrap();
110        let container = self.op.handle(config.handle());
111        S::new(config, container)
112    }
113}