Why expect function works unexpectedly?
Opened this issue · 4 comments
When I run this code:
expect( 'func_1' )
->once()
->with( 'krya' )
->andReturn( 'krya' );
$this->assertSame( 'krya', func_1( 'ne-krya' ) );
Result: Failed asserting that null is identical to 'krya'.
. It looks well.
But when I run this code:
expect( 'func_1' )
->with( 'krya' )
->once()
->andReturn( 'krya' );
$this->assertSame( 'krya', func_1( 'ne-krya' ) );
OK (1 test, 1 assertion)
Works but why?
I came across this problem as well and as far as I can tell, this manifests itself like this:
Functions\expect
does not validate arguments given inwith()
if there is no times quantifier likeonce()
- Adding
once()
afterwith()
affectsandReturn()
, but still does not validate arguments given inwith()
Code in the SUT (error_log
only for demonstration purposes)
$a = get_post_meta($postId, 'a', true);
$b = get_post_meta($postId, 'b', true);
error_log("a is '$a' and b is '$b'");
add_term_meta($termId, 'a', $a, true);
add_term_meta($termId, 'b', $b, true);
once()
after with()
This was my initial setup in the test. Note that the get_post_meta
calls are in a different order than in the SUT ('b' before 'a').
expect('get_post_meta')->with(1, 'b', true)->once()->andReturn('B');
expect('get_post_meta')->with(1, 'a', true)->once()->andReturn('A');
The error_log
in the SUT shows this: a is 'B' and b is 'A'
. The values given in andReturn()
have been returned in the order they were defined, indepentent of the arguments given in with()
.
once()
before with()
Putting once()
before with()
did fix this behaviour.
expect('get_post_meta')->once()->with(1, 'b', true)->andReturn('B');
expect('get_post_meta')->once()->with(1, 'a', true)->andReturn('A');
The error_log
in the SUT shows: a is 'A' and b is 'B'
Without once()
Leaving out once()
causes the arguments in with()
to be ignored and the value in andReturn()
is returned for every call to this function.
expect('get_post_meta')->with(1, 'b', true)->andReturn('B');
expect('get_post_meta')->with(1, 'a', true)->andReturn('A');
The error_log
in the SUT shows: a is 'B' and b is 'B'
The incorrect value (a = B) should be discovered by this test:
expect('add_term_meta')->with(5, 'a', 'A', true);
expect('add_term_meta')->with(5, 'b', 'B', true);
This check will not fail, because once()
has been omitted.
Conclusion
In case with()
is used, Functions\expect
should enforce the use of a times quantifier like once()
as the first call after expect()
. Alternatively, once()
could be the default if no quantifier is passed.
Currently, checking passed arguments with with()
while not using once()
before with()
causes the test to pass, even when the values do not match.
I can confirm that this is an issue, and that @abrain's assessment above is correct.
expect()
->once() // Must be present, and must be first
->with() // Otherwise this is not validated
->andReturn(); // When this is present
@wppunk @abrain, I'm sorry for the late answer here.
Has anyone of you tested if this is something that applies to Mockery or it is something that only happens on Brain Monkey?
I've just validated that this issue does not apply to Mockery.
(Mockery::mock('SomeClass'))
->shouldReceive()
->with() // This is validated, without needing `->once()`
->andReturn(); // And this is present