[geary/wip/794174-conversation-monitor-max-cpu: 13/14] Make new unit test methods and the mock object a bit more useful.
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/794174-conversation-monitor-max-cpu: 13/14] Make new unit test methods and the mock object a bit more useful.
- Date: Fri, 6 Apr 2018 23:57:50 +0000 (UTC)
commit 6392364f192f79c7f4a0110fd9702d92fcb0f2ca
Author: Michael James Gratton <mike vee net>
Date: Fri Mar 9 12:03:07 2018 +1100
Make new unit test methods and the mock object a bit more useful.
* test/mock-object.vala: Make ExpectedCall object public, use a
style API to add additional expectations to it. Make MockObject
always throw on unexpected call, so ActualCall and related
machinery can be removed.
* test/test-case.vala: Add some additional assert methods, allow
null actual values where it makes sense. Add TestCase
wait_for_call and wait_for_signal for better async testing.
test/mock-object.vala | 176 +++++++++++++++++++++++++++++--------------------
test/test-case.vala | 86 +++++++++++++++++++++++-
2 files changed, 187 insertions(+), 75 deletions(-)
---
diff --git a/test/mock-object.vala b/test/mock-object.vala
index 2902ce5..59b4b56 100644
--- a/test/mock-object.vala
+++ b/test/mock-object.vala
@@ -44,98 +44,111 @@ private class IntArgument : Object, Argument {
}
+/**
+ * Represents an expected method call on a mock object.
+ *
+ * An instance of this object is returned when calling {@link
+ * Mock.Object.expect_call}, and may be used to further specify
+ * expectations, such that the mock method should throw a specific
+ * error or return a specific value or object.
+ */
+public class ExpectedCall : Object {
-public interface MockObject {
+ public string name { get; private set; }
+ internal Object[]? args;
+ public Error? throw_error { get; private set; default = null; }
+ public Object? return_object { get; private set; default = null; }
+ public Variant? return_value { get; private set; default = null; }
+ public bool was_called { get; private set; default = false; }
- public static Object box_arg<T>(T value) {
- return new BoxArgument<T>(value);
- }
- public static Object int_arg(int value) {
- return new IntArgument(value);
+ internal ExpectedCall(string name, Object[]? args) {
+ this.name = name;
+ this.args = args;
}
- public class ExpectedCall : Object {
+ public ExpectedCall returns_object(Object value) {
+ this.return_object = value;
+ return this;
+ }
+ public ExpectedCall returns_boolean(bool value) {
+ this.return_value = new GLib.Variant.boolean(value);
+ return this;
+ }
- public string name { get; private set; }
- internal Object[]? args;
- public Error? throw_error { get; private set; default = null; }
- public Object? return_object { get; private set; default = null; }
- public Variant? return_value { get; private set; default = null; }
+ public ExpectedCall @throws(Error err) {
+ this.throw_error = err;
+ return this;
+ }
+ internal void called() {
+ this.was_called = true;
+ }
- internal ExpectedCall(string name, Object[]? args) {
- this.name = name;
- this.args = args;
- }
+}
- public ExpectedCall returns_object(Object value) {
- this.return_object = value;
- return this;
- }
- public ExpectedCall returns_boolean(bool value) {
- this.return_value = new GLib.Variant.boolean(value);
- return this;
- }
+/**
+ * Denotes a class that is injected into code being tested.
+ *
+ * Mock objects are unit testing fixtures that are used to provide
+ * instances of specific classes or interfaces which are required by
+ * the code being tested. For example, if an object being tested
+ * requires certain objects to be passed in via its constructor or as
+ * arguments of method calls and uses these to implement its
+ * behaviour, mock objects that fulfil these requirements can be used.
+ *
+ * Mock objects provide a means of both ensuring code being tested
+ * makes expected method calls with expected arguments on its
+ * dependencies, and a means of orchestrating the return value and
+ * exceptions raised when these methods are called, if any.
+ *
+ * To specify a specific method should be called on a mock object,
+ * call {@link expect_call} with the name of the method and optionally
+ * the arguments that are expected. The returned {@link ExpectedCall}
+ * object can be used to specify any exception or return values for
+ * the method. After executing the code being tested, call {@link
+ * assert_expectations} to ensure that the actual calls made matched
+ * those expected.
+ */
+public interface MockObject {
- public ExpectedCall @throws(Error err) {
- this.throw_error = err;
- return this;
- }
+ public static Object box_arg<T>(T value) {
+ return new BoxArgument<T>(value);
}
-
- protected struct ActualCall {
- string name;
- Object[] args;
+ public static Object int_arg(int value) {
+ return new IntArgument(value);
}
-
- protected abstract Gee.List<ExpectedCall> expected { get; set; }
- protected abstract Gee.List<ActualCall?> actual { get; set; }
+ protected abstract Gee.Queue<ExpectedCall> expected { get; set; }
public ExpectedCall expect_call(string name, Object[]? args = null) {
ExpectedCall expected = new ExpectedCall(name, args);
- this.expected.add(expected);
+ this.expected.offer(expected);
return expected;
}
- public void assert_expectations() throws Error{
- int calls = 0;
- foreach (ExpectedCall expected in this.expected) {
- string context = "Call #%d, %s".printf(calls, expected.name);
- ActualCall? actual = null;
- if (calls < this.actual.size) {
- actual = this.actual.get(calls++);
- }
-
- assert_true(actual != null, context + " not made");
- assert_string(expected.name, actual.name, context);
- if (expected.args != null) {
- assert_args(expected.args, actual.args, context);
- }
- }
+ public void assert_expectations() throws Error {
+ assert_true(this.expected.is_empty,
+ "%d expected calls not made".printf(this.expected.size));
+ reset_expectations();
+ }
- assert_int(this.expected.size, this.actual.size,
- "Number of calls");
+ public void reset_expectations() {
+ this.expected.clear();
}
protected bool boolean_call(string name, Object[] args, bool default_return)
throws Error {
- ExpectedCall? expected = get_next_expected();
- this.actual.add({ name, args });
-
- if (expected.throw_error != null) {
- throw expected.throw_error;
- }
+ ExpectedCall? expected = call_made(name, args);
bool return_value = default_return;
- if (expected != null && expected.return_value != null) {
+ if (expected.return_value != null) {
return_value = expected.return_value.get_boolean();
}
return return_value;
@@ -143,26 +156,45 @@ public interface MockObject {
protected R object_call<R>(string name, Object[] args, R default_return)
throws Error {
- ExpectedCall? expected = get_next_expected();
- this.actual.add({ name, args });
-
- if (expected.throw_error != null) {
- throw expected.throw_error;
- }
+ ExpectedCall? expected = call_made(name, args);
R? return_object = default_return;
- if (expected != null && expected.return_object != null) {
+ if (expected.return_object != null) {
return_object = (R) expected.return_object;
}
return return_object;
}
- private inline ExpectedCall? get_next_expected() {
- ExpectedCall? next = null;
- if (this.expected.size > this.actual.size) {
- next = this.expected.get(this.actual.size);
+ protected R object_or_throw_call<R>(string name, Object[] args, Error default_error)
+ throws Error {
+ ExpectedCall? expected = call_made(name, args);
+
+ if (expected.return_object != null) {
+ throw default_error;
+ }
+ return expected.return_object;
+ }
+
+ protected void void_call(string name, Object[] args) throws Error {
+ call_made(name, args);
+ }
+
+ private ExpectedCall? call_made(string name, Object[] args) throws Error {
+ assert_false(this.expected.is_empty, "Unexpected call: %s".printf(name));
+
+ ExpectedCall expected = this.expected.poll();
+ assert_string(expected.name, name, "Unexpected call");
+ if (expected.args != null) {
+ assert_args(expected.args, args, "Call %s".printf(name));
}
- return next;
+
+ expected.called();
+
+ if (expected.throw_error != null) {
+ throw expected.throw_error;
+ }
+
+ return expected;
}
private void assert_args(Object[]? expected_args, Object[]? actual_args, string context)
diff --git a/test/test-case.vala b/test/test-case.vala
index 7c85a1f..78502d2 100644
--- a/test/test-case.vala
+++ b/test/test-case.vala
@@ -22,15 +22,31 @@
*/
-public void assert_equal(Object expected, Object actual, string? context = null)
+public void assert_null(Object? actual, string? context = null)
+ throws Error {
+ if (actual != null) {
+ print_assert(context ?? "Object is non-null", null);
+ assert_not_reached();
+ }
+}
+
+public void assert_non_null(Object? actual, string? context = null)
+ throws Error {
+ if (actual == null) {
+ print_assert(context ?? "Object is null", null);
+ assert_not_reached();
+ }
+}
+
+public void assert_equal(Object expected, Object? actual, string? context = null)
throws Error {
if (expected != actual) {
- print_assert(context ?? "Objects not equal", null);
+ print_assert(context ?? "Objects are not equal", null);
assert_not_reached();
}
}
-public void assert_string(string expected, string actual, string? context = null)
+public void assert_string(string expected, string? actual, string? context = null)
throws Error {
if (expected != actual) {
string a = expected;
@@ -62,6 +78,14 @@ public void assert_true(bool condition, string? context = null)
}
}
+public void assert_false(bool condition, string? context = null)
+ throws Error {
+ if (condition) {
+ print_assert(context ?? "Expected false", null);
+ assert_not_reached();
+ }
+}
+
public void assert_error(Error expected, Error? actual, string? context = null) {
bool failed = false;
if (actual == null) {
@@ -118,6 +142,17 @@ private inline void print_assert(string message, string? context) {
public abstract class TestCase : Object {
+
+ private class SignalWaiter : Object {
+
+ public bool was_fired = false;
+
+ public void @callback(Object source) {
+ was_fired = true;
+ }
+ }
+
+
protected MainContext main_loop = MainContext.default();
private GLib.TestSuite suite;
@@ -170,6 +205,51 @@ public abstract class TestCase : Object {
return result;
}
+ /**
+ * Waits for a mock object's call to be completed.
+ *
+ * This method busy waits on the test's main loop until either
+ * until {@link ExpectedCall.was_called} is true, or until the
+ * given timeout in seconds has occurred.
+ *
+ * Returns //true// if the call was made, or //false// if the
+ * timeout was reached.
+ */
+ protected bool wait_for_call(ExpectedCall call, double timeout = 1.0) {
+ GLib.Timer timer = new GLib.Timer();
+ timer.start();
+ while (!call.was_called && timer.elapsed() < timeout) {
+ this.main_loop.iteration(false);
+ }
+ return call.was_called;
+ }
+
+ /**
+ * Waits for an object's signal to be fired.
+ *
+ * This method busy waits on the test's main loop until either
+ * until the object emits the named signal, or until the given
+ * timeout in seconds has occurred.
+ *
+ * Returns //true// if the signal was fired, or //false// if the
+ * timeout was reached.
+ */
+ protected bool wait_for_signal(Object source, string name, double timeout = 0.5) {
+ SignalWaiter handler = new SignalWaiter();
+ ulong id = GLib.Signal.connect_swapped(
+ source, name, (GLib.Callback) handler.callback, handler
+ );
+
+ GLib.Timer timer = new GLib.Timer();
+ timer.start();
+ while (!handler.was_fired && timer.elapsed() < timeout) {
+ this.main_loop.iteration(false);
+ }
+
+ source.disconnect(id);
+ return handler.was_fired;
+ }
+
private class Adaptor {
public string name { get; private set; }
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]