Tony Montana
Tony Montana

Reputation: 1018

PHPUnit: Better ways to test REST APIs which does CRUD operation

I have created one REST API, which does CRUD operation on DB and returns the result from DB. Now, this is my below code:

class CreateRecords
{
    public function createEntities($details)
    {
        if (trim($details['username']) == "") {
            $this->result = "Username is empty.";
        } else {
            $this->result = create_record($userDetails);
        }       
        return $this->result;
    }
}

Here, in this above code, create_record is a function which does validation part and insert the details into db and returns the id of the newly created record.

Now, currently my test case is like this:

class TestCreateRecords
{   
    public function setUp()
    {
        $this->client = new GuzzleHttp\Client([
                'base_uri' => 'http://localhost/test/'
            ]);
    }

    public function testCreateEntitiesAPI()
    {           
        $response = $this->client->post('service.php', [
            'json' => [
                'username' => '[email protected]',
                'user_password' => 'test',
                'first_name' => 'Test',
                'last_name' => 'test',
                'email1' => '[email protected]'
            ]
        ]);

        $actual_response = json_decode($response->getBody(), true);

        $this->assertArrayHasKey("id", $actual_response, 'Un-successful.');
    }
}

So, I have few queries regarding this:

  1. In my main class file, create_record function validates the username before inserting details into DB. It checks if the username is already available or not. If available, it won't create a record and sends response as Already Available. If not available, then it will create a record and sends newly created unique id.

    Now, I want to test both the scenarios, where passed username is already available in DB and not available in DB. Now, with my above test case, I'll have to make changes in it every time before running the test, so I can test the scenario, where username is already not available in DB. And it seems illogical to me.

    I would like to know, is there any other way I can accomplish this?

  2. Someone advised me to mock the create_record method to test the API and write another test case to test the create_record method itself. But as per my application MVC framework, it seems impossible for me, right now. Because in create_record method, there are dozens of methods which are being called and these methods calls few others methods. And again, I'll have to mock those dozens methods and write the test cases for them and so on.

    It is time and effort consuming for us. So I have decided to not to mock any methods and test the whole API flow in one test case.

    I would like to know, is it a good approach? And what will be the side-effects of it?

Upvotes: 0

Views: 842

Answers (1)

mickadoo
mickadoo

Reputation: 3483

Whoever advised you to try mocking is right. It's great that you want to start testing more, but it often turns up problems in application architecture.

Unit tests should ideally test just a single class or function. If all of your functions are calling other functions all the way down to the database insertion it's an end-to-end test. It's fine to have these, but in your case you want to test a variety of cases (when validation fails because username exists) and you'll soon find that testing all your edge cases in end-to-end tests is slow.

You already have a CreateRecords class. Where is this used in the service.php class? If you're using some sort of dependency injection you can mock the CreateRecords class in the controller (which I'm guessing is service.php for you. In this way you can test the service separately from the API code. Here's an example:

/**
 * Controller action
 */
function myAction() {
  if ($this->isAdmin()) {
    throw new \Exception('You need to be an admin');
  }

  $this->userService->create($_POST);
}

/**
 * Service containing actions related to users
 */
class UserService {

  /**
   * @var UserValidator
   */
  protected $validator;

  /**
   * @var RecordSaver
   */
  protected $recordSaver;

  /**
   * @param UserValidator $validator
   * @param RecordSaver $recordSaver
   */
  public function __construct(
    UserValidator $validator, 
    RecordSaver $recordSaver
  ) {
    $this->validator = $validator;
    $this->recordSaver = $recordSaver;
  }

  /**
   * @param $params
   * @return array
   */
  public function create($params) {
    $this->validator->validate($params);

    return $this->recordSaver->save($params);
  }
}

/**
 * Validation of new users / or updating existing
 */
class UserValidator {
  /**
   * @param array $params
   * @throws Exception
   */
  public function validate($params) {
    if (!isset($params['username'])) {
      throw new \Exception('Username must be set');
    }
  }
}

/**
 * Wrapper for database calls
 */
class RecordSaver {
  /**
   * @param array $params
   * @return array
   */
  public function save($params) {
    return create_record($params);
  }
}

Then you can create tests that test each layer only, e.g.

  • A test that checks if the API will throw an exception if not an admin
  • A lot of different unit tests for user validation
  • A few tests for user creation in UserService (mocking the validator and record saver)

Upvotes: 1

Related Questions