Posts Tagged ‘functional’

Functional test on uploads for a web service – a workaround

Wednesday, August 4th, 2010 by Franz Stelzer

Yesterday i was faced with a problem on implementing a functional test for a web service, which handles a file upload.
However this workflow seemed to be pretty easy so, as it is documented in the Jobeet Tutorial:

1
2
3
4
5
6
7
8
9
10
11
12
$browser->get('/job/new')->
with('request')->begin()->
isParameter('module', 'job')->
isParameter('action', 'new')->
end()->

click('Preview your job', array('job' => array(
'company' => 'Sensio Labs',
'url' => 'http://www.sensio.com/',
'logo' => sfConfig::get('sf_upload_dir').'/jobs/sensio-labs.gif',
// other parameters ...
));

First the form is called and rendered from the job/new page and than the test browser clicks on the submit button. The call of the click method is the most problematic point of this issue. This click method does some magic and assigns the given file from an absolute path to the request. This magic will not be done in a normal post request. Trying to submit a file directly with the post action during a functional test will end up to empty file values in the action code.

This will not work, also our web service would require it:

1
$browser->post('/job/new', array('job' => array('company' => 'Sensio Labs', 'logo' => sfConfig::get('sf_upload_dir').'/jobs/sensio-labs.gif',') ))->....

Now we have a bad situation: The web service requires the post request, but the functional test does only work with the click method.

Searching a while through the web exposed that i am not the only one with this problem. Similar topics or questions could be read in the forum, on stackoverflow or on the user mailing list.

The workaround for this problem is as easy as ugly: We have to render a form for this functional test but have to avoid that this rendered form is accessible in the producation environment.

The action code of route test/upload looks like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public function executeUpload(sfWebRequest $request)
{
  $this->form = new TestUploadForm();
  if($request->isMethod('post'))
  {
    $this->form->bind($request->getParameter('upload'), $request->getFiles($this->form->getName()));
 
    if($this->form->isValid())
    {
      // make something with the form ...
      $values = $this->form->getValues();
      $file = $values['file'];
      $filepath = sfConfig::get('sf_data_dir').'/test/some.thing';
      $file->save($filepath);
    }
    else
    {
      // make some error handling ...
    }
  }
  else
  {
    // only render form in dev or test environment
    if(in_array(sfConfig::get('sf_environment'), array('dev', 'test')))
    {
      throw new sfException('not enabled for this environment');
    }
  }
}

For better understanding, this action saves the incoming file always to the same location.

The template for the faked form rendering looks like (standard template for rendering a form)

1
2
3
4
5
6
7
8
9
10
<?php echo form_tag($some_route, array('multipart' => true)); ?>
  <table>
    <?php echo $form; ?>
      <tr>
        <td colspan="2">
          <input type="submit" value="GO" />
        </td>
      </tr>
    </table>
</form>

After calling test/upload in your browser, you should see this rendered form now. This form is rendered in the test environment, too.
The functional test is now able to handle this form as it would be placed on a normal website.

The functional test code could look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$generatedFile = sfConfig::get( 'sf_data_dir' ).'/test/some.thing';
 
// cleanup
@unlink($generatedFile);
 
// do the fake request
$browser->
  get('/test/upload')->
    with('request')->begin()->
      isParameter('module', 'test')->
      isParameter('action', 'upload')->
    end()->
 
// do the real web service request
  click('GO', array( 'upload' => array( 'name' => 'foo' , 'file' => $testfile)))->
    with('request')->begin()->
      isParameter('module', 'test')->
      isParameter('action', 'upload')->
    end()->
 
    with('response')->begin()->
      isStatusCode(200)->
    end();
 
// action should have saved the incoming file to the expected location
$this->assertTrue(file_exists($generatedFile));

This works now like the example from the Jobeet Tutorial. First we make our workaround and fake a rendered form in the get request. Afterwards we make the post request within the click call.
When everything went fine, the incoming file should be copied to the expected file location.

I am not proud of this workaround, yet it is the only simple solution doing this trick without hacking the symfony core code.
If you have any other ideas or better solutions, please let me know!