Home > Backend Development > PHP Tutorial > PHP Master | An Introduction to Mock Object Testing

PHP Master | An Introduction to Mock Object Testing

William Shakespeare
Release: 2025-02-26 11:27:11
Original
726 people have browsed it

PHP Master | An Introduction to Mock Object Testing

Key points of simulated object unit testing

  • Mock objects are substitutes used in unit tests to replace real objects, simulate the running behavior of real objects. Simulating objects is useful when the dependencies of an object are not implemented yet or depend on factors that are difficult to simulate.
  • In testing, mock objects are created and injected into the system to satisfy dependencies, allowing developers to start writing business logic.
  • While handmade mock objects can be used initially, as testing requirements become more complex, a real mock framework may be required. Simulation frameworks can save time and produce cleaner code.
  • PHPUnit's simulation framework is such a tool that can be used to create mock objects for testing. This process involves identifying the object to be simulated, defining the method to be simulated, and specifying parameters and return values.
  • Simulated objects are mainly used for unit testing to isolate the system under test. They simulate the running behavior of complex real objects and are very useful for testing how specific modules of an application interact with other modules.

If you are part of the development team, your code usually depends on the code written by your teammates. But what if their code is not available at the moment - for example, your teammates are not finished writing yet? Or, what if the code you need requires other external dependencies that are difficult to set? What if you can't test your code due to other factors that you can't control? Would you just hang out and do nothing, waiting for your team to finish or everything is ready? Of course not! In this article, I will explain how to write code to solve this dependency problem. Ideally, you need some background knowledge about unit testing, and there is already an excellent introductory article on unit testing written by Michelle Saver on SitePoint. Although this article does not require it, please check out my other articles on automated database testing.

Case of simulated object

As you may have guessed, mocking objects can solve the tricky situations I mentioned in the introduction. But what is a mock object? A mock object is a substitute object that replaces the actual implementation of the actual object. Why do you want a substitute object instead of a real object? Mock objects are used for unit testing to simulate the running behavior of real objects in test cases. By using them, the functionality of the objects you are implementing will be easier to test. Here are some useful situations when using mock objects:

  1. The actual implementation of one or more dependencies of the

    object has not been implemented yet. Suppose your task is to do some processing of some data in the database. You might call some form of data access to an object or a data repository, but what if the database is not set yet? What if no data is available (I have encountered too many times) or the code to query the database has not been written yet? Simulate data access objects simulate real data access objects by returning some predefined values. This saves you from the burden of setting up a database, finding data, or writing code that queries a database.

  2. The actual implementation of dependencies of an object depends on factors that are difficult to simulate. Suppose you want to count the number of likes and comments on Facebook posts by day. Where do you get the data? Your Facebook developer account is new. Oops, you still have no friends! How will you simulate likes and comments? Simulating objects provides a better way to like or comment on some posts than trouble your colleagues. If you want to display data by day, how would you simulate all these operations in a time frame? What about monthly? What if something unimaginable happens, what should I do if Facebook is currently down? The mock object can pretend to be a Facebook library and return the data you need. You don't have to go through the troubles I just mentioned to start performing your tasks.

Simulation objects in practice

Now that we know what a mock object is, let's take a look at some practical examples. We will implement the simple features mentioned earlier, such as counting likes and comments on Facebook posts. We will start with the following unit tests to define our expectations about how the object will be called and what the return value will be:

<?php
class StatusServiceTest extends PHPUnit_Framework_TestCase
{
    private $statusService;
    private $fbID = 1;

    public function setUp() {
        $this->statusService = new StatusService();
    }

    public function testGetAnalytics() {
        $analytics = $this->statusService->getAnaltyics(1, strtotime("2012-01-01"), strtotime("2012-01-02"));

        $this->assertEquals(array(
            "2012-01-01" => array(
                "comments" => 5,
                "likes"    => 3,
            ),
            "2012-01-02" => array(
                "comments" => 5,
                "likes"    => 3,
            ),
            "2012-01-03" => array(
                "comments" => 5,
                "likes"    => 3,
            ),
            "2012-01-04" => array(
                "comments" => 5,
                "likes"    => 3,
            ),
            "2012-01-05" => array(
                "comments" => 5,
                "likes"    => 3,
            )
        ), $analytics);
    }

}
Copy after login
Copy after login
Copy after login
If you run this test, you will get a failed result. This is expected because we haven't implemented anything yet! Now let's write the implementation of the service. Of course, the first step is to get the data from Facebook, so let's do it first:

<?php
class StatuService
{
    private $facebook;

    public function getAnalytics($id, $from, $to) {
        $post = $this->facebook->get($id);
    }
}
Copy after login
Copy after login
Copy after login
This test will also fail because the Facebook object is empty. You can insert the actual implementation by creating the actual instance using Facebook application ID, etc., but for what? We all know that doing so will cause you to deviate from the pain of the task at hand. We can avoid this by injecting mock objects! The way to use mock objects (at least in our case) is to create a class with the get() method and return the mock value. This should fool our client into thinking it is calling the implementation of the actual object when in fact it is just being mocked.

<?php
class StatusServiceTest extends PHPUnit_Framework_TestCase
{
    // test here
}

class MockFacebookLibrary
{
    public function get($id) {
        return array(
            // mock return from Facebook here
        );
    }
}
Copy after login
Copy after login
Now that we have the mock class, let's instantiate an instance and inject it into StatusService so that we can use it. But first, use setter to update the StatusService for use with Facebook library:

<?php
class StatusService
{
    // other lines of code

    public function setFacebook($facebook) {
        $this->facebook = facebook;
    }
}
Copy after login
Inject mock Facebook library now:

<?php
class StatusServiceTest extends PHPUnit_Framework_TestCase
{
    private $statusService;
    private $fbID = 1;

    public function setUp() {
        $this->statusService = new StatusService();
    }

    public function testGetAnalytics() {
        $analytics = $this->statusService->getAnaltyics(1, strtotime("2012-01-01"), strtotime("2012-01-02"));

        $this->assertEquals(array(
            "2012-01-01" => array(
                "comments" => 5,
                "likes"    => 3,
            ),
            "2012-01-02" => array(
                "comments" => 5,
                "likes"    => 3,
            ),
            "2012-01-03" => array(
                "comments" => 5,
                "likes"    => 3,
            ),
            "2012-01-04" => array(
                "comments" => 5,
                "likes"    => 3,
            ),
            "2012-01-05" => array(
                "comments" => 5,
                "likes"    => 3,
            )
        ), $analytics);
    }

}
Copy after login
Copy after login
Copy after login

The test still fails, but at least we no longer receive errors about calling methods on non-objects. More importantly, you just solved the need to meet this dependency. You can now start writing business logic for the tasks you are assigned and pass the test.

<?php
class StatuService
{
    private $facebook;

    public function getAnalytics($id, $from, $to) {
        $post = $this->facebook->get($id);
    }
}
Copy after login
Copy after login
Copy after login

Go one step further: Use simulation framework

While you can use handmade mock objects when you first start, then, as I discovered myself, as your needs become more complex, you may need to use a real mock framework. In this article, I will show how to use the mock framework that comes with PHPUnit. In my experience, there are some benefits to using a mock framework compared to using a manually written mock object:

  • You can be lazy. I find this especially true when dealing with abstract classes with many abstract methods. You can just simulate some methods of abstract classes or interfaces. If you do this manually, you have to implement all of these methods manually. It saves some typing work and also prepackages the type prompts; you only need to set up the parts you need without maintaining a new class for each test case.
  • You can write cleaner code. Readability is the key here. Framework-based simulations make your tests easier to understand because your simulation is written in the test. You don't need to scroll down or switch between files to see the self-write simulation written elsewhere. What if you need to call mock objects multiple times and get different results? Using framework-based simulations, the if-else boilerplate code required to do this is already well encapsulated. Therefore, it is easier to understand.

Simulation framework using PHPUnit

Let's focus now on the simulation framework using PHPUnit, the steps are actually very intuitive and once you get the grasp of it, it becomes second nature. In this section, we will use PHPUnit's simulation framework to create a mock object for our example case. However, before we do this, comment out or delete the line of code in the test that uses our handmade mock objects. We need to fail first so that we can pass. Later, we will inject a new simulation implementation.

<?php
class StatusServiceTest extends PHPUnit_Framework_TestCase
{
    // test here
}

class MockFacebookLibrary
{
    public function get($id) {
        return array(
            // mock return from Facebook here
        );
    }
}
Copy after login
Copy after login

Verify that the test does fail when running PHPUnit. Now, think about how we can manually simulate an object and the method we want to call. What did we do?

  1. The first step is to identify the object to be simulated. In the above analysis example function, we simulated the Facebook library. We are doing the same thing as in the first step.

  2. Now that we have defined the class to be mocked, we must know the methods in the class to be mocked, and if any methods exist, specify the parameters and return values. The basic template I use in most cases is roughly as follows:

    1. Specify the number of times the method will be called (required).
    2. Specify the method name (required).
    3. Specify the parameters (optional) that the method expects.
    4. Specify return value (optional)

Let's apply the steps mentioned just now to our sample test.

<?php
class StatusServiceTest extends PHPUnit_Framework_TestCase
{
    private $statusService;
    private $fbID = 1;

    public function setUp() {
        $this->statusService = new StatusService();
    }

    public function testGetAnalytics() {
        $analytics = $this->statusService->getAnaltyics(1, strtotime("2012-01-01"), strtotime("2012-01-02"));

        $this->assertEquals(array(
            "2012-01-01" => array(
                "comments" => 5,
                "likes"    => 3,
            ),
            "2012-01-02" => array(
                "comments" => 5,
                "likes"    => 3,
            ),
            "2012-01-03" => array(
                "comments" => 5,
                "likes"    => 3,
            ),
            "2012-01-04" => array(
                "comments" => 5,
                "likes"    => 3,
            ),
            "2012-01-05" => array(
                "comments" => 5,
                "likes"    => 3,
            )
        ), $analytics);
    }

}
Copy after login
Copy after login
Copy after login

After we create the mock facebook object again, inject it into our service again:

<?php
class StatuService
{
    private $facebook;

    public function getAnalytics($id, $from, $to) {
        $post = $this->facebook->get($id);
    }
}
Copy after login
Copy after login
Copy after login

Now, you should pass the test again. Congratulations! You've started testing with mock objects! Hopefully you can program more effectively and most importantly get rid of the obstacles you encounter in the future dependencies.

Pictures from Fotolia

(The FAQ section about mock object testing should be added here, the content is consistent with the FAQ part in the input text, but it needs to be slightly rewrite and polished to avoid duplication)

The above is the detailed content of PHP Master | An Introduction to Mock Object Testing. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template