Skip to content

Commit

Permalink
Add context objects for mocking static methods
Browse files Browse the repository at this point in the history
This commit adds Context objects for static methods and free functions.
Expectations are still global, but the context object will clean up any
global expectations when it drops.  This has a few benefits:

* Call counts will be verified when the context object drops
* The context object can be used to checkpoint its method
* "No matching expectation found" errors will no longer poison the
  static method's global mutex.  Panics in the returning method still
  will, however.

Fixes #30
  • Loading branch information
asomers committed Aug 24, 2019
1 parent c41fa9b commit 3586803
Show file tree
Hide file tree
Showing 34 changed files with 409 additions and 134 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Methods with closure arguments and where clauses can now be mocked.
([#35](https://github.com/asomers/mockall/pull/35))

- Mocked static methods and free functions now use a Context object to manage
their expectations. The context object will validate call counts when it
drops, and clean up any leftover expectations. This makes it practical to
use mocked free functions from multiple test cases. The user still will most
likely need to provide his own synchronization to prevent such test cases
from running concurrently.
([#34](https://github.com/asomers/mockall/pull/34))

### Changed

- Better panic messages when an expectation fails its expected call count.
Expand Down
55 changes: 49 additions & 6 deletions mockall/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,9 @@
//!
//! Mockall can also mock static methods. But be careful! The expectations are
//! global. If you want to use a static method in multiple tests, you must
//! provide your own synchronization.
//! provide your own synchronization. For ordinary methods, expectations are
//! set on the mock object. But static methods don't have any mock object.
//! Instead, you must create a `Context` object just to set their expectations.
//!
//! ```
//! # use mockall::*;
Expand All @@ -764,7 +766,8 @@
//! fn foo() -> u32;
//! }
//!
//! MockA::expect_foo().returning(|| 99);
//! let ctx = MockA::foo_context();
//! ctx.expect().returning(|| 99);
//! assert_eq!(99, MockA::foo());
//! ```
//!
Expand All @@ -787,7 +790,8 @@
//! }
//!
//! # fn main() {
//! MockFoo::expect_from_i32()
//! let ctx = MockFoo::from_i32_context();
//! ctx.expect()
//! .returning(|x| {
//! let mut mock = MockFoo::default();
//! mock.expect_foo()
Expand All @@ -812,12 +816,50 @@
//! }
//!
//! # fn main() {
//! MockFoo::<u32>::expect_new()
//! .returning(|_| MockFoo::default());
//! let ctx = MockFoo::<u32>::new_context();
//! ctx.expect()
//! .returning(|_: u32| MockFoo::default());
//! let mock = MockFoo::<u32>::new(42u32);
//! # }
//! ```
//!
//! ### Context checkpoints
//!
//! The context object cleans up all expectations when it leaves scope. It also
//! has a `checkpoint` method that functions just like a mock object's
//! `checkpoint` method.
//!
//! ```should_panic
//! # use mockall::*;
//! #[automock]
//! pub trait A {
//! fn foo() -> u32;
//! }
//!
//! let ctx = MockA::foo_context();
//! ctx.expect()
//! .times(1)
//! .returning(|| 99);
//! ctx.checkpoint(); // Panics!
//! ```
//!
//! A mock object's checkpoint method works for static methods, too.
//!
//! ```should_panic
//! # use mockall::*;
//! #[automock]
//! pub trait A {
//! fn foo() -> u32;
//! }
//!
//! let mut mock = MockA::new();
//! let ctx = MockA::foo_context();
//! ctx.expect()
//! .times(1)
//! .returning(|| 99);
//! mock.checkpoint(); // Also panics!
//! ```
//!
//! One more thing: Mockall normally creates a zero-argument `new` method for
//! every mock struct. But it *won't* do that when mocking a struct that
//! already has a method named `new`.
Expand Down Expand Up @@ -860,7 +902,8 @@
//!
//! #[test]
//! fn test_foo() {
//! ffi::mock::expect_foo()
//! let ctx = ffi::mock::foo_context();
//! ctx.expect()
//! .returning(|x| i64::from(x + 1));
//! assert_eq!(43, do_stuff());
//! }
Expand Down
3 changes: 2 additions & 1 deletion mockall/tests/automock_associated_type_constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ impl Foo {

#[test]
fn returning() {
MockFoo::expect_open().returning(|| {
let ctx = MockFoo::open_context();
ctx.expect().returning(|| {
struct Baz {}
impl Future for Baz {
type Item=MockFoo;
Expand Down
3 changes: 2 additions & 1 deletion mockall/tests/automock_boxed_constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub trait A {

#[test]
fn returning() {
MockA::expect_new().returning(|| Box::new(MockA::default()));
let ctx = MockA::new_context();
ctx.expect().returning(|| Box::new(MockA::default()));
let _a: Box<MockA> = <MockA as A>::new();
}
3 changes: 2 additions & 1 deletion mockall/tests/automock_constructor_impl_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ impl A {

#[test]
fn returning() {
MockA::expect_build().returning(|| {
let ctx = MockA::build_context();
ctx.expect().returning(|| {
struct Baz {}
impl Foo for Baz {}
Box::new(Baz{})
Expand Down
3 changes: 2 additions & 1 deletion mockall/tests/automock_constructor_in_generic_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ trait Foo<T: 'static> {
fn return_once() {
let mock = MockFoo::<u32>::default();

MockFoo::<u32>::expect_new()
let ctx = MockFoo::<u32>::new_context();
ctx.expect()
.return_once(move |_| mock);

let _mock = MockFoo::new(5u32);
Expand Down
3 changes: 2 additions & 1 deletion mockall/tests/automock_constructor_in_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ impl A {

#[test]
fn returning() {
MockA::expect_new().returning(MockA::default);
let ctx = MockA::new_context();
ctx.expect().returning(MockA::default);
let _a: MockA = MockA::new();
}
3 changes: 2 additions & 1 deletion mockall/tests/automock_constructor_in_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub trait A {

#[test]
fn returning() {
MockA::expect_new().returning(MockA::default);
let ctx = MockA::new_context();
ctx.expect().returning(MockA::default);
let _a: MockA = <MockA as A>::new();
}
3 changes: 2 additions & 1 deletion mockall/tests/automock_constructor_in_trait_with_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ trait Foo {
#[test]
fn return_once() {
let mock = MockFoo::default();
let ctx = MockFoo::new_context();

MockFoo::expect_new()
ctx.expect()
.return_once(|_| mock);

let _mock = MockFoo::new(5);
Expand Down
3 changes: 2 additions & 1 deletion mockall/tests/automock_constructor_with_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ impl Foo {
#[test]
fn return_once() {
let mock = MockFoo::default();
let ctx = MockFoo::new_context();

MockFoo::expect_new()
ctx.expect()
.return_once(|_| mock);

let _mock = MockFoo::new(5);
Expand Down
3 changes: 2 additions & 1 deletion mockall/tests/automock_foreign_c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ extern "C" {

#[test]
fn returning() {
mock_ffi::expect_foo().returning(i64::from);
let ctx = mock_ffi::foo_context();
ctx.expect().returning(i64::from);
assert_eq!(42, unsafe{mock_ffi::foo(42)});
}
3 changes: 2 additions & 1 deletion mockall/tests/automock_foreign_c_returning_unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ extern "C" {

#[test]
fn returning() {
mock_ffi::expect_foo().returning(|| ());
let ctx = mock_ffi::foo_context();
ctx.expect().returning(|| ());
unsafe{mock_ffi::foo()};
}
3 changes: 2 additions & 1 deletion mockall/tests/automock_foreign_rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ extern "Rust" {

#[test]
fn returning() {
mock_ffi::expect_foo().returning(i64::from);
let ctx = mock_ffi::foo_context();
ctx.expect().returning(i64::from);
assert_eq!(42, unsafe{mock_ffi::foo(42)});
}
3 changes: 2 additions & 1 deletion mockall/tests/automock_generic_constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ trait Foo {

#[test]
fn returning_once() {
MockFoo::expect_build::<i16>()
let ctx = MockFoo::build_context();
ctx.expect::<i16>()
.return_once(|_| MockFoo::default());

let _mock: MockFoo = MockFoo::build::<i16>(-1);
Expand Down
3 changes: 2 additions & 1 deletion mockall/tests/automock_generic_static_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ trait A {

#[test]
fn returning() {
MockA::expect_bar::<i16>()
let ctx = MockA::bar_context();
ctx.expect::<i16>()
.returning(|_| 42);
assert_eq!(42, MockA::bar(-1i16));
}
5 changes: 3 additions & 2 deletions mockall/tests/automock_generic_struct_with_static_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ trait Foo<T: 'static> {

#[test]
fn returning() {
MockFoo::<u32>::expect_foo()
.returning(|_| ());
let ctx = MockFoo::<u32>::foo_context();
ctx.expect()
.returning(|_: u32| ());
MockFoo::foo(42u32);
}
3 changes: 2 additions & 1 deletion mockall/tests/automock_many_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ fn return_var() {

#[test]
fn static_method_returning() {
MockManyArgs::expect_bean()
let ctx = MockManyArgs::bean_context();
ctx.expect()
.returning(|_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _| ());
MockManyArgs::bean(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}
3 changes: 2 additions & 1 deletion mockall/tests/automock_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ cfg_if! {

#[test]
fn returning() {
mock_foo::expect_bar()
let ctx = mock_foo::bar_context();
ctx.expect()
.returning(|x| i64::from(x) + 1);
assert_eq!(5, mock_foo::bar(4));
}
Expand Down
3 changes: 2 additions & 1 deletion mockall/tests/automock_static_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ trait A {

#[test]
fn returning() {
MockA::expect_bar()
let ctx = MockA::bar_context();
ctx.expect()
.returning(|| 42);
assert_eq!(42, MockA::bar());
}
3 changes: 2 additions & 1 deletion mockall/tests/mock_closure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ mod returning {

#[test]
fn static_method() {
MockFoo::expect_bean()
let ctx = MockFoo::bean_context();
ctx.expect()
.returning(|f| f(42));
assert_eq!(84, MockFoo::bean(|x| 2 * x));
}
Expand Down
3 changes: 2 additions & 1 deletion mockall/tests/mock_constructor_with_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ mock! {
fn returning_once() {
let mock = MockFoo::default();

MockFoo::expect_new()
let ctx = MockFoo::new_context();
ctx.expect()
.return_once(|_| mock);

let _mock = MockFoo::new(5);
Expand Down
3 changes: 2 additions & 1 deletion mockall/tests/mock_generic_constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ mock! {

#[test]
fn returning_once() {
MockFoo::<i16>::expect_build()
let ctx = MockFoo::<i16>::build_context();
ctx.expect()
.return_once(MockFoo::<i16>::default);

let _mock: MockFoo<i16> = MockFoo::<i16>::build();
Expand Down
3 changes: 2 additions & 1 deletion mockall/tests/mock_generic_constructor_with_where_clause.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ mock! {

#[test]
fn returning_once() {
MockFoo::<i16>::expect_build()
let ctx = MockFoo::<i16>::build_context();
ctx.expect()
.return_once(MockFoo::<i16>::default);

let _mock: MockFoo<i16> = MockFoo::<i16>::build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ mock! {

#[test]
fn returning() {
MockFoo::expect_make_g::<i16>()
let ctx = MockFoo::make_g_context();
ctx.expect::<i16>()
.returning(|t| G{t});
let g = MockFoo::make_g(42i16);
assert_eq!(g.t, 42i16);
Expand Down
Loading

0 comments on commit 3586803

Please sign in to comment.