/* * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ private interface Argument { public abstract void assert(Object object) throws Error; } private class BoxArgument : Object, Argument { private T value; internal BoxArgument(T value) { this.value = value; } public new void assert(Object object) throws Error { assert_true( object is BoxArgument, "Expected %s value".printf(this.get_type().name()) ); assert_true(this.value == ((BoxArgument) object).value); } } private class IntArgument : Object, Argument { private int value; internal IntArgument(int value) { this.value = value; } public new void assert(Object object) throws Error { assert_true(object is IntArgument, "Expected int value"); assert_int(this.value, ((IntArgument) object).value); } } /** * 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 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; } 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; } public ExpectedCall @throws(Error err) { this.throw_error = err; return this; } internal void called() { this.was_called = true; } } /** * 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 fulfill 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 static Object box_arg(T value) { return new BoxArgument(value); } public static Object int_arg(int value) { return new IntArgument(value); } protected abstract Gee.Queue expected { get; set; } public ExpectedCall expect_call(string name, Object[]? args = null) { ExpectedCall expected = new ExpectedCall(name, args); this.expected.offer(expected); return expected; } public void assert_expectations() throws Error { assert_true(this.expected.is_empty, "%d expected calls not made".printf(this.expected.size)); reset_expectations(); } public void reset_expectations() { this.expected.clear(); } protected bool boolean_call(string name, Object[] args, bool default_return) throws Error { ExpectedCall? expected = call_made(name, args); bool return_value = default_return; if (expected.return_value != null) { return_value = expected.return_value.get_boolean(); } return return_value; } protected R object_call(string name, Object[] args, R default_return) throws Error { ExpectedCall? expected = call_made(name, args); R? return_object = default_return; if (expected.return_object != null) { return_object = (R) expected.return_object; } return return_object; } protected R object_or_throw_call(string name, Object[] args, GLib.Error default_error) throws GLib.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)); } 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) throws Error { int args = 0; foreach (Object expected in expected_args) { if (args >= actual_args.length) { break; } Object actual = actual_args[args]; string arg_context = "%s, argument #%d".printf(context, args++); if (expected is Argument) { ((Argument) expected).assert(actual); } else if (expected != null) { assert_true( actual != null, "%s: Expected %s, actual is null".printf( arg_context, expected.get_type().name() ) ); assert_true( expected.get_type() == actual.get_type(), "%s: Expected %s, actual: %s".printf( arg_context, expected.get_type().name(), actual.get_type().name() ) ); assert_equal( expected, actual, "%s: object value".printf(arg_context) ); } else { } } assert_int( expected_args.length, actual_args.length, "%s: argument list length".printf(context) ); } }