Permalink
Browse files

Initial commit of the basic library and the README.

  • Loading branch information...
0 parents commit e54aae72b76ccc12295af5aa4701f43eeea6b1aa @jnthn committed Jul 3, 2010
Showing with 99 additions and 0 deletions.
  1. +43 −0 README
  2. +56 −0 lib/Test/Mock.pm
@@ -0,0 +1,43 @@
+Test::Mock is a module that works alongside the standard Test module to
+help you write tests when you want to verify what methods are called on
+an object, while still having calls to undefined methods die.
+
+You get started just as normal with the test file, but also add a use
+statement for Test::Mock.
+
+ use Test;
+ use Test::Mock;
+
+ plan 2;
+
+Imagine we have some class Foo:
+
+ class Foo {
+ method lol() { 'rofl' }
+ method wtf() { 'oh ffs' }
+ }
+
+We then arrange to have a mocked instance of this class. This means that
+instead of calls to lol and wtf actually resulting in the methods being
+invoked, it simply logs the invocations.
+
+ my $x = mocked(Foo);
+
+We can then take the actions that should result in some method calls.
+Here we just make them directly, but you'd probably no doubt pass the
+mock to other objects that will make calls on it.
+
+ $x.lol();
+ $x.lol();
+
+When you're done, you assert that the things you expected to happen
+actually happened.
+
+ check-mock($x,
+ *.called('lol', times => 2),
+ *.never-called('wtf'),
+ );
+
+And that's it. :-) A few more features coming soon too.
+
+Enjoy!
@@ -0,0 +1,56 @@
+use Test;
+
+class Test::Mock::Log {
+ has @!log-entries;
+
+ method log-method-call($name, $capture) {
+ @!log-entries.push({ :$name, :$capture });
+ }
+
+ method called($name, :$times) {
+ my @calls = @!log-entries.grep({ .<name> eq $name });
+ if defined($times) {
+ ok +@calls == $times, "called $name $times time{ $times != 1 && 's' }";
+ }
+ else {
+ ok ?@calls, "called $name";
+ }
+ }
+
+ method never-called($name) {
+ my @calls = @!log-entries.grep({ .<name> eq $name });
+ ok !@calls, "never called $name";
+ }
+};
+
+module Test::Mock {
+ sub mocked($type) is export {
+ # Generate a subclass that logs each method call.
+ my %already-seen = :new;
+ my $mocker = ClassHOW.new;
+ $mocker.^add_parent($type.WHAT);
+ for $type, $type.^parents() -> $p {
+ last if $p === Mu;
+ for $p.^methods(:local) -> $m {
+ unless %already-seen{$m.name} {
+ $mocker.^add_method($m.name, (method (|$c) {
+ $!log.log-method-call($m.name, $c);
+ }).clone);
+ %already-seen{$m.name} = True;
+ }
+ }
+ }
+
+ # Add log attribute and a method to access it.
+ $mocker.^add_attribute(Attribute.new( name => '$!log', has_accessor => False ));
+ $mocker.^add_method('!mock_log', method { $!log });
+
+ # Return a mock object.
+ my $mocked = $mocker.^compose();
+ return $mocked.new(log => Test::Mock::Log.new());
+ }
+
+ sub check-mock($mock, *@checker) is export {
+ .($mock!mock_log) for @checker;
+ }
+}

0 comments on commit e54aae7

Please sign in to comment.