I'm looking for any solution to allow the expect(value).to.be
syntax above.
Keep it simple then:
fn main() {
expect(4).to.be.equal_to(3);
}
fn expect<T>(actual: T) -> To<T> {
let be = Be {
be: Expectation(actual),
};
To { to: be }
}
struct To<T> {
pub to: Be<T>,
}
struct Be<T> {
pub be: Expectation<T>,
}
struct Expectation<T>(T);
impl<T> Expectation<T> {
fn equal_to<U>(&self, expected: U)
where
U: PartialEq<T>,
{
if expected != self.0 {
panic!("Report error")
}
}
}
optionally want to skip [to
and be
]
use std::{ops::Deref, rc::Rc};
fn main() {
expect(4).to.be.equal_to(3);
expect(4).to.equal_to(3);
expect(4).equal_to(3);
}
fn expect<T>(actual: T) -> Expectation<T> {
let core = Core(Rc::new(actual));
let be = Be { core: core.clone() };
let to = To {
be,
core: core.clone(),
};
Expectation {
to,
core: core.clone(),
}
}
struct Expectation<T> {
pub to: To<T>,
core: Core<T>,
}
impl<T> Deref for Expectation<T> {
type Target = Core<T>;
fn deref(&self) -> &Core<T> {
&self.core
}
}
struct To<T> {
pub be: Be<T>,
core: Core<T>,
}
impl<T> Deref for To<T> {
type Target = Core<T>;
fn deref(&self) -> &Core<T> {
&self.core
}
}
struct Be<T> {
core: Core<T>,
}
impl<T> Deref for Be<T> {
type Target = Core<T>;
fn deref(&self) -> &Core<T> {
&self.core
}
}
struct Core<T>(Rc<T>);
impl<T> Clone for Core<T> {
fn clone(&self) -> Self {
Core(self.0.clone())
}
}
impl<T> Core<T> {
fn equal_to<U>(&self, expected: U)
where
U: PartialEq<T>,
{
if expected != *self.0 {
panic!("Report error")
}
}
}
Some macros would reduce duplication, but I was too lazy to actually show that ;-)
When in Rome...
I would try to play to Rust's strengths when designing a test assertions library. To me, this means using traits to allow people to easily add custom assertions.
use crate::testlib::prelude::*;
fn main() {
expect(4).to(be.equal_to(3));
expect(4).to(equal_to(3));
}
mod testlib {
// Shorthand variants that will always be imported.
// Minimize what's in here to avoid name collisions
pub mod prelude {
use super::*;
pub fn expect<A>(actual: A) -> Expectation<A> {
Expectation::new(actual)
}
#[allow(non_upper_case_globals)]
pub static be: Be = Be;
pub fn equal_to<E>(expected: E) -> EqualTo<E> {
EqualTo::new(expected)
}
}
// All the meat of the implementation. Can be divided up nicely.
pub trait Assertion<A> {
fn assert(&self, actual: &A);
}
pub struct Expectation<A>(A);
impl<A> Expectation<A> {
pub fn new(actual: A) -> Self {
Expectation(actual)
}
pub fn to(&self, a: impl Assertion<A>) {
a.assert(&self.0)
}
}
pub struct Be;
impl Be {
pub fn equal_to<E>(&self, expected: E) -> EqualTo<E> {
EqualTo::new(expected)
}
}
pub struct EqualTo<E>(E);
impl<E> EqualTo<E> {
pub fn new(expected: E) -> Self {
EqualTo(expected)
}
}
impl<A, E> Assertion<A> for EqualTo<E>
where
A: PartialEq<E>,
{
fn assert(&self, actual: &A) {
if *actual != self.0 {
panic!("report an error")
}
}
}
}
Next steps I'd look into:
- An assertion should probably report failures to some passed-in struct, not panic.
- Add a
to_not
and/or not_to
negative matcher.
- Add composition of assertions.