New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Partial mock is not working with $this #33

Closed
cabello opened this Issue Aug 23, 2011 · 24 comments

Comments

Projects
None yet
@cabello
Copy link

cabello commented Aug 23, 2011

Hi,

I am having problem with partial mock and use of $this inside of it.

I will put some code sample to be clearer.

In my test I have something like below:

$serviceMock = Mockery::mock('Service');
$serviceMock->shouldReceive('spam')->andReturn('eggs');

$foobar = Mockery::mock(new Foobar($foo));
$foobar->shouldReceive('getService')
    ->andReturn($serviceMock);

$foobar->useService();

In my real code I have something like this:

class Foobar {
    public function __construct($foo) { 
        $this->foo = $foo;
    }

    public function useService() {
        $bacon = $this->foo->bacon();

        $service = $this->getService();
        $eggs = $service->spam();

        return $bacon + $eggs;
    }

    public function getService() {
        return new RealService(); // hits network and I don't want to hit it
    }
}

I was migrating a PHPUnit test to Mockery and I had this problem, I believe one way to do it is using Injection, something like setService($service), but right now I have a huge code base that works with PHPUnit. Is this a bug of Mockery or something expected to happen?

Thanks for your help!

@rlepidi

This comment has been minimized.

Copy link

rlepidi commented Aug 24, 2011

I actually just posted this same issue an hour ago, #32. I think it's an issue with how proxy objects are handled. To be "clean", mockery allows you to pass in an existing object. The problem is mockery can't modify existing objects. PHPUnit gets around this by calling the constructor for you and creating a new class and instantiating that.

Mockery needs something like that.

@cabello

This comment has been minimized.

Copy link

cabello commented Aug 24, 2011

I saw your bug right after posting the bug.

I was debugging the code and thought the same thing, If we can do something like:

$foobar = Mockery::mock('new Foobar', array($foo));

It would work, but would be clear enough?

Or we could use a new protocol like Mockery::partialMock('Foobar', array($foo));

I don't know, but I will try to implement this feature soon.

@cabello cabello closed this Aug 24, 2011

@cabello cabello reopened this Aug 24, 2011

@jnankin

This comment has been minimized.

Copy link

jnankin commented Sep 22, 2011

Any updates? I'm running into this (no pun intended) as well.

@hermann-kosselowski

This comment has been minimized.

Copy link

hermann-kosselowski commented Sep 28, 2011

I'm having problems with this, too.
Is anyone working on this, already?

@zburnham

This comment has been minimized.

Copy link

zburnham commented Dec 19, 2011

I think I'm having a related issue. I'm attempting to create a partial mock, and am having trouble with Mockery knowing about methods in the object being 'mocked' (I have that in quotes because in this case it's working with an actual concrete instance of the class). When the object is created, the constructor is called (as you would expect) but any methods called in the constructor do "not exist on this mock object". This is the error:

BadMethodCallException: Method ::SetRGBArray() does not exist on this mock object

Does anyone have any suggestions or advice?

@robertbasic

This comment has been minimized.

Copy link
Collaborator

robertbasic commented Jan 28, 2012

With a slightly different code, this works as it should, see robertbasic@c34710c

The only difference I did was to mock the object like m::mock('Foobar[baz]') instead of doing m::mock(new Foobar);

@robertbasic

This comment has been minimized.

Copy link
Collaborator

robertbasic commented Jan 30, 2012

Looks like I'm running into this one as well!

$request->setModuleName('foo')
    ->setControllerName('bar');

$mock->shouldReceive('setControllerName')
    ->with('bar')
    ->once();

fails. Will dig into it once again.

@robertbasic

This comment has been minimized.

Copy link
Collaborator

robertbasic commented Jan 30, 2012

Of course an easy workaround is:

$mock->shouldReceive('setModuleName')
    ->andReturn($mock)
    ->shouldReceive('setControllerName')
    // and so on...
@adeslade

This comment has been minimized.

Copy link

adeslade commented Feb 2, 2012

Cheers for that @robertbasic

@cabello

This comment has been minimized.

Copy link

cabello commented Feb 4, 2012

It works when using the string form, for me this issue is closed. :D

@cabello cabello closed this Feb 4, 2012

@robertbasic

This comment has been minimized.

Copy link
Collaborator

robertbasic commented Feb 4, 2012

@cabello care to explain how are you using it?

@cabello

This comment has been minimized.

Copy link

cabello commented Feb 4, 2012

Like you have said: "The only difference I did was to mock the object like m::mock('Foobar[baz]') instead of doing m::mock(new Foobar);"

So instead of letting mockery work on a real object instance, we use the string form 'Foobar[method]' and mockery builds the instance for us with selected methods mocked, that works pretty fine on our project.

@robertbasic

This comment has been minimized.

Copy link
Collaborator

robertbasic commented Feb 20, 2012

@jnankin use \Mockery::mock('TestingUtils[getData]'); instead and it'll pass.

I agree that it should be fixed if possible. I have put a lot of hours into trying to find a fix, but failed. Until @padraic can come up with a proper fix, we're left to use these workarounds :)

HTH!

@jnankin

This comment has been minimized.

Copy link

jnankin commented Feb 20, 2012

Oh, I see. I was thinking the [] was some sort of syntax for calling the constructor. not passing in methods to mock.

My bad.

@robertbasic

This comment has been minimized.

Copy link
Collaborator

robertbasic commented Feb 20, 2012

Yup. The [foo,bar] syntax tells mockery to mock only those methods, the others are left untouched. It's explained in more detail in the readme file + have a look at the unit tests :)

@davidwinter

This comment has been minimized.

Copy link

davidwinter commented Mar 18, 2012

Thanks for pointing out the workaround in this ticket.

@wjzijderveld

This comment has been minimized.

Copy link

wjzijderveld commented Mar 20, 2012

Does anyone has a solution for this, when your constructor requires arguments?
Although I still rather have the partial mocking of an object working :)

btw: I kinda like this ticket to reopen. The documentation explains this should work, so either the documentation should be adjusted, or this is a bug that can be fixed.

@adeslade

This comment has been minimized.

Copy link

adeslade commented Mar 21, 2012

@wjzijderveld I got round that issue by adding setters for the constructor arguments :)

@wjzijderveld

This comment has been minimized.

Copy link

wjzijderveld commented Mar 21, 2012

We currently call the constructor manually after mocking the object.
But that means the test needs to be able to know and create the dependencies for that constructor, also not wanted.

But I still would like to be able to create a Mock from an instance and be able to mock a method that is only called internally by the instance.

@robertbasic

This comment has been minimized.

Copy link
Collaborator

robertbasic commented Mar 21, 2012

Fork it and fix it?

@wjzijderveld

This comment has been minimized.

Copy link

wjzijderveld commented Mar 21, 2012

I already forked it and wrote a test for it. Didnt figure out how to fix it yet :)

@CMCDragonkai

This comment has been minimized.

Copy link

CMCDragonkai commented Nov 19, 2013

I'm getting something similar to this:

        $api_client = Mockery::mock('Httpful\Request');
        $api_client->shouldReceive('post')->andReturn($api_client);
        $api_client->shouldReceive('addHeader')->andReturn($api_client);
        $api_client->shouldReceive('timeout')->andReturn($api_client);
        $api_client->shouldReceive('body')->andReturn($api_client);
        $api_client->shouldReceive('send')->andReturn($response_object);

Using $this when not in object context
in C:\www\snapsearch_clients\php\SnapSearch-Client-PHP\vendor\mockery\mockery\library\Mockery\Generator.php(129) : eval()'d code:482

The thing I'm trying to mock is this: https://github.com/nategood/httpful/blob/master/src/Httpful/Request.php

And the call I'm making in the code:

            $response = $this->api
                        ->post($this->api_url)
                        ->addHeader('Authorization', 'Token ' . $this->api_key)
                        ->timeout(30)
                        ->body($this->request_parameters, 'json')
                        ->send()
                        ->raw_body;

Some of the methods are static functions that are also chainable.

@aik099

This comment has been minimized.

Copy link
Contributor

aik099 commented Nov 19, 2013

@CMCDragonkai, you should use special ->andReturnSelf() method when you need method to return mock reference for method chaining.

@davedevelopment

This comment has been minimized.

Copy link
Collaborator

davedevelopment commented Nov 19, 2013

Could you provide a minimal test case? I can't get this to fail:

<?php

require "vendor/autoload.php";

use Mockery as m;

$mock = m::mock('Httpful\Request');
$mock->shouldReceive('post')->andReturn($mock);
$mock->shouldReceive('addHeader')->andReturn($mock);
$mock->shouldReceive('timeout')->andReturn($mock);
$mock->shouldReceive('body')->andReturn($mock);
$mock->shouldReceive('send')->andReturn(123);

echo $mock->post("http://example.com")
    ->addHeader('Authorization', 'Token ' . 123)
    ->timeout(30)
    ->body(['dave' => 'dave'], 'json')
    ->send();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment