Git Product home page Git Product logo

visibility-extension's Introduction

atoum/visibility-extension example workflow name

The atoum visibility-extension allows you to override method visibility in your unit tests. For example, you will be able to test protected method with it.

Example

In the example, we test the protected method bar:

<?php

namespace
{
  class foo
  {
    protected function bar()
    {
      return 'foo';
    }
  }
}

namespace tests\units
{
  use atoum\atoum;

  class foo extends atoum\test
  {
    public function testBar()
    {
      $this
        ->if($sut = new \foo())
        ->then
          ->string($this->invoke($sut)->bar())->isEqualTo('foo')
      ;
    }
  }
}

Install it

Install extension using composer:

composer require --dev atoum/visibility-extension

The extension will be automatically loaded. If you ever want to unload it, you can add this to your configuration file:

<?php

// .atoum.php

use atoum\atoum\visibility;

$runner->removeExtension(visibility\extension::class);

Use it

You can achieve visibility override using two methods:

  • Override concrete classes' methods visibility: this will allow you to assert on protected methods returned values,
  • Override mocked classes' methods visibility: this will allow you to override protected methods code and assert on their calls.

Override concrete classes' methods visibility

Overriding methods visibility is done on-the-fly in unit tests body using the invoke method:

<?php

namespace
{
	class foo
	{
		protected function bar()
		{
			return $this;
		}
		
		protected function baz($arg)
		{
			return $arg;
		}
	}
}

namespace tests\units
{
	use atoum\atoum;

	class foo extends atoum\test
	{
		public function testBar()
		{
			$this
				->if($sut = new \foo())
				->then
					->object($this->invoke($sut)->bar())->isIdenticalTo($sut)
			;
		}
		
		public function testBaz()
		{
			$this
				->if($sut = new \foo())
				->and($arg = uniqid())
				->then
					->variable($this->invoke($sut)->baz($arg))->isEqualTo($arg)
			;
		}
	}
}

As you can see, we only used the invoke method. It has a special syntax that we are going to detail: $this->invoke(<object instance>)-><method name>(<arguments>)

  • <object instance> is a reference to an object instance. In the previous example it was $foo, a reference to a \foo instance;
  • <method name> is the name of the method we want to make visible and call. In the previous example it was baror baz.;
  • <arguments> is the arguments list you want to pass to the method. In the previous example it was $arg, a string generated with uniqid().

Override mocked classes' methods visibility

Overriding mocked classes' methods requires a bit more work, involving the mock generator. Before detailing how to achieve that, keep in mind that there are some limitations you have to be aware of. We'll detail the just after a short example:

<?php

namespace
{
	class foo
	{
		public function baz()
		{
			return $this->bar();
		}

		protected function bar()
		{
			$args = func_get_args();

			return sizeof($args) ? $args : $this;
		}
	}
}

namespace tests\units
{
	use atoum\atoum;

	class foo extends atoum\test
	{
		public function testBar()
		{
			$this
				->given(
					$this->mockGenerator
						->makeVisible('bar')
						->generate('foo')
				)
				->if($mockedSut = new \mock\foo)
				->and($this->calling($mockedSut)->bar = 'foo')
				->then
					->string($mockedSut->baz())->isEqualTo('foo')
					->string($mockedSut->baz())->isEqualTo('foo')
					->mock($mockedSut)
						->call('bar')->twice()
			;
		}
	}
}

The mock generator now provides a makeVisible method which you can call to override a method visibility. You have to call this method before the generation of the mocked class which happens the first time a mock is instanciated or when you call the generate method of the mock controller.

Doing this will create a child class (the mock) and define the protected methods as public. You will then be able to call them directly, without even using the invoke method we saw in the previous section.

You will also be able to assert on those methods' calls using standard atoum assertions.

Now let's talk about the limitations:

  • The first one is that the visibility override has to be declared before the first generation of the mocked class,
  • Once the visibility has been overridden, it can't be reverted,
  • Overriding the visibility of method in mocks has to be done carefully: it is a permanent operation involving reflection.

When you want to temporarily override the visibility of a mock class method, you can change the name of the mocked class using the generate method's arguments. Using the previous example, it would look like:

<?php

namespace tests\units
{
	use atoum\atoum;

	class foo extends atoum\test
	{
		public function testBar()
		{
			$this
				->given(
					$this->mockGenerator
						->makeVisible('bar')
						->generate('foo', 'mock', 'mockedFoo')
				)
				->if($mockedSut = new \mock\mockedFoo)
				->and($this->calling($mockedSut)->bar = 'foo')
				->then
					->string($mockedSut->baz())->isEqualTo('foo')
					->string($mockedSut->baz())->isEqualTo('foo')
					->mock($mockedSut)
						->call('bar')->twice()
			;
		}
	}
}

Doing this, we would generate a \mock\mockedFoo class from the \foo class with a looser visiblity on the bar method. This will allow us to bypass some limitation:

  • This will generate a new mock class so the visibility override will always apply, even if the \foo class was already mocked,
  • We can "revert" this operation by treating this mock class as a one-shot mock and forget it right after the test. This will still require that we don't reuse the same name for future mocks.

Links

License

visibility-extension is released under the BSD-3 Clause License. See the bundled LICENSE file for details.

atoum

visibility-extension's People

Contributors

agallou avatar grummfy avatar hywan avatar jubianchi avatar offchan42 avatar tarlepp avatar vaidas-lungis avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

visibility-extension's Issues

Configure the build matrix

  • Remove hhvm and hhvm-nightly from the matrix and allowed failures
  • Add PHP 7.0
  • Add PHP nightly
  • Add two environments for composer : --prefer-stable and --prefer-lowest
  • Merge before_script and script
  • Add IRC notifications
  • Fix the version constraint on atoum/atoum: it should be at least ^2.0
  • Disable code coverage on PHP >= 7.0 if there are segfaults (see here)
  • The PHP nightly should be an allowed failure

--prefer-lowest may not pass on php >= 7.0: in this case, disabling --prefer-lowest on php >= 7.0 is OK.

See here fro an example

Unable to override mocked private methods

Let's take this example :

<?php

namespace titi
{
    class foo
    {
        public function baz()
        {
            return $this->bar();
        }

        private function bar()
        {
            return 'fail';
        }
    }
}

namespace titi\tests\units
{
    use mageekguy\atoum;

    class foo extends atoum\test
    {
        public function testBar()
        {
            $this
                ->given(
                    $this->mockGenerator
                        ->makeVisible('bar')
                        ->generate('\titi\foo')
                )
                ->if($mockedSut = new \mock\titi\foo)
                ->and($this->calling($mockedSut)->bar = 'foo')
                ->then
                    ->string($mockedSut->baz())->isEqualTo('foo')
                    ->string($mockedSut->baz())->isEqualTo('foo')
                    ->mock($mockedSut)
                        ->call('bar')->twice()
            ;
        }
    }
}

(or directy this gist : https://gist.github.com/agallou/5a7fe3b2598449271f5a )
(that's almost the same as the example in the README)

when running this test we have this error :

In file /home/agallou/tests/test-atoum-visibility/test.php on line 36, mageekguy\atoum\asserters\phpString() failed: strings are not equal
-Expected
+Actual
@@ -1 +1 @@
-string(3) "foo"
+string(4) "fail"

When running the same test with bar as a protected method, it pass.

In the README, there is

Doing this will create a child class (the mock) and define the protected/private methods as public

So, private methods cannot be override in a mock, it's the "real method" that is . Is this normal ? (this could be useful, for example if a private method does a request, and we want to check the parameters without doing the request).

Installation failed with atoum v4

Hello,
I cant't install this extension with atoum v4.

vendor/bin/atoum -v
atoum version dev-master by Frédéric Hardy (/mnt/c/Projects/DOCKER/xxx/vendor/atoum/atoum)
`Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Root composer.json requires atoum/visibility-extension ^1.3 -> satisfiable by atoum/visibility-extension[1.3.0].
    - atoum/visibility-extension 1.3.0 requires atoum/atoum ^2.9 | ^3.0 -> found atoum/atoum[2.9.0, 2.9.1, 3.0.0, ..., 3.4.2] but it conflicts with your root composer.json require (^4.0).`

The only solution is to downgrade atoum?

Regards

Make class/mock properties visible

Why

The documentation only talks about method visibility... but nothing about property.

Question

Is it possible to make class/mock properties visible? And howto (maybe update the documentation accordingly).

Add a license

We should have a license and it should be the same as the atoum/atoum repository

  • a LICENSE file
  • in the composer.json file (BSD-3-Clause)
  • in the README.md file

create a new tag

There is no tag since the 26874e7 commit, that allows the new method of adding extensions to the runner.

So we can't use it on a .atoum.php file without beeing on dev-master.

Is that ok if I create a 1.0.2 tag for the visibility-extension ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.