I have an ExampleModel that calls to an ExampleService that retrieves data from our backend. I can't figure out how to write unit tests for my application; which is structured as shown below:
ExampleService
public function retrieveMyToDoList(parameters):Promise
{
var promise:Promise = performRequest({request: "call to backend", parameters: values, session_id: clientModel.sessionID});
promise.addResultProcessor(parseRetrieveToDoListResult);
return promise;
}
protected function parseRetrieveToDoListResult(data:Object, callback:Function):void
{
does some JSON parsing into an object
callback(null, object containing my retrieved data)
}
ExampleModel
public function getMyToDoList():Promise
{
var promise:Promise = exampleService.retrieveToDoList(parameters);
promise.addResultHandler(onGetToDoListResult);
promise.addErrorHandler(onGetToDoListError);
return promise;
}
private function onGetHeadrsByUserResult(promise:Promise):void
{
// where this event will be listened to by mediators etc
dispatchEvent(new ResponseEvent(GOOD_RESULT));
}
private function onGetHeadrsByUserError(promise:Promise):void
{
dispatchEvent(new ResponseEvent(BAD_RESULT));
}
I'm trying to use asmock to mock my Service so that I can test my Model and how it handles the various results in the resulting Object but how do I mock the callback? I saw examples where the return values were mocked but in my case I'm using the Promise and callback and I'm not too sure how to go ahead.
If someone could please advise.
Thanks!
You can let the mock service return a real promise and call the handleResult method of the promise directly.
FYI: it's not a good idea to have a direct dependency from the model to the service. You should let the service manipulate the model, or pass the results from the service to a command which will manipulate the model. Models should never depend on anything else than helper classes.
Related
Newbie in coldbox so please have patience with me.
I am trying to implement TDD on my coldbox application.
Under my service model I inject this dependency.
property name="wirebox" inject="wirebox"
property name="populator" inject="wirebox:populator";
On my service model I have this method. GetallUsers()
User function new(){
return wirebox.getInstance("User");
}
function getAllUsers(){
var users= queryExecute(
"SELECT * FROM USERS",
{},
{returnType="array"}
).map(function(user){
return populator.populateFromStruct(new(),user);
});
return users;
}
And on my UserServiceTest I have this code:
component extends="coldbox.system.testing.BaseModelTest" model="models.UserService"{
/*********************************** LIFE CYCLE Methods ***********************************/
function beforeAll(){
super.beforeAll();
// setup the model
super.setup();
// init the model object
model.init();
}
function afterAll(){
super.afterAll();
}
/*********************************** BDD SUITES ***********************************/
function run(){
describe( "Users Suite", function(){
it( "can get list of users", function(){
var stubPopulator = stub().$( 'populateFromStruct', {} );
model.$property( 'populator', 'variables', stubPopulator );
var users= model.getAll();
expect( event.getPrivateValue( "users") ).toBeStruct();
});
});
}
But I got this error saying **variable [POPULATOR] doesn't exist**.
Hoping someone can help me.
You didn't show the full test bundle, but based on the name it would appear it is a unit test (or a ColdBox model test, which is a type of unit test). Unit tests do not spin up the ColdBox framework by default and do not process injections for the CFCs under test. They are created "naked" and it's up to you to provide mocks for an dependencies that CFC has.
So in this case, you'd need to provide a mock populator to your model to be used for the test. So something like this:
var stubPopulator = createStub().$( 'populateFromStruct', {} )
model.$property( 'populator', 'variables', stubPopulator )
var users= model.getAll();
My stubed populator just returns an empty struct. It's also worth noting I don't think your queryMap() is returning a struct like you think it is so you may need to confirm the functionality of that method.
Alternatively, you could switch to more of an integration test where you set this.loadColdBox to true in your test CFC's pseduo-constructor and then use getInstance( 'UserService' ) to get a fully built instance of your UserService which would have the populator injected into it. Exactly how this would look like depends on several things you haven't shared such as your test harness setup, and your test bundle CFC's base class.
I want to test a specific controller action in Zend Framework 3. Because I use ZfcUser (https://github.com/ZF-Commons/ZfcUser) and Bjyauthorize (https://github.com/bjyoungblood/BjyAuthorize) I need to mock some view helpers. For example I need to mock isAllowed view helper and let it return true always:
class MyTest extends AbstractControllerTestCase
{
public function setUp()
{
$this->setApplicationConfig(include 'config/application.config.php');
$bootstrap = \Zend\Mvc\Application::init(include 'config/application.config.php');
$serviceManager = $bootstrap->getServiceManager();
$viewHelperManager = $serviceManager->get('ViewHelperManager');
$mock = $this->getMockBuilder(IsAllowed::class)->disableOriginalConstructor()->getMock();
$mock->expects($this->any())->method('__invoke')->willReturn(true);
$viewHelperManager->setService('isAllowed', $mock);
$this->getApplication()->getServiceManager()->setAllowOverride(true);
$this->getApplication()->getServiceManager()->setService('ViewHelperManager', $viewHelperManager);
}
public function testViewAction()
{
$this->dispatch('/myuri');
$resp = $this->getResponse();
$this->assertResponseStatusCode(200);
#$this->assertModuleName('MyModule');
#$this->assertMatchedRouteName('mymodule/view');
}
}
In my view.phtml (which will be rendered by opening/dispatching /myuri uri) I call the view helper $this->isAllowed('my-resource').
But I got response code 500 with failing exception when executing testViewAction():
Exceptions raised:
Exception 'Zend\ServiceManager\Exception\ServiceNotFoundException' with message 'A plugin by the name "isAllowed" was not found in the plugin manager Zend\View\HelperPluginManager' in ../vendor/zendframework/zend-servicemanager/src/AbstractPluginManager.php:131
How can I inject my isAllowed mock to the view helper manager in a way, that let the test case (testViewAction / $this->dispatch()) pass.
As stated in the previous answer already, we need to override the ViewHelper within the ViewHelperManager in the application object. The following code shows how this could be achieved:
public function setUp()
{
$this->setApplicationConfig(include 'config/application.config.php');
$bootstrap = \Zend\Mvc\Application::init(include 'config/application.config.php');
$serviceManager = $bootstrap->getServiceManager();
// mock isAllowed View Helper of Bjyauthorize
$mock = $this->getMockBuilder(IsAllowed::class)->disableOriginalConstructor()->getMock();
$mock->expects($this->any())->method('__invoke')->willReturn(true);
// inject the mock into the ViewHelperManager of the application
$this->getApplication()->getServiceManager()->get('ViewHelperManager')->setAllowOverride(true);
$this->getApplication()->getServiceManager()->get('ViewHelperManager')->setService('isAllowed', $mock);
}
ViewHelperManager is an other instance of service manager. and it's not allowed to override source code. Can you try "setAllowOverride" before "setService" method?
I am using the CakePHP-ReST-DataSource-Plugin Datasource for hitting a RESTful service in my model. This implies that the models will not have a database connection.
I have successfully accessed the services and would now like to write unit tests for the models. This is proving to be a daunting task since I cannot succeed to mock the datasource so that I do not hit the actual remote Service but rather return expected results for the tests.
<?php
App::uses('KnowledgePoint', 'Model');
class KnowledgePointTest extends CakeTestCase{
public $fixtures = array('app.knowledgepoint');
public $useDbConfig = 'RestTest';
private $KnowledgePoint;
public function setUp() {
parent::setUp();
$this->KnowledgePoint = ClassRegistry::init('KnowledgePoint');
/**
* This is the confusing part. How would I mock the datasource
so that I can mock the request method which returns the data
from the api?
*/
$this->KnowledgePoint->DataSource = $this->getMockForModel(
'RestSource',array('request'));
}
public function tearDown() {
parent::tearDown();
}
}
I would like to be able to mock the datasource and stub the request method to return data that would normally be returned from the remote service.
Kind regards,
Roland
Mocking the model and its getDataSource() method so that it returns your mocked datasource should theoretically work. Here's an example
App::uses('RestSource', 'Rest.Model/Datasource');
$DataSource = $this->getMock('RestSource', array('request'), array(array()));
$DataSource
->expects($this->any())
->method('request')
->will($this->returnValue('some custom return value'));
$Model = $this->getMockForModel('KnowledgePoint', array('getDataSource'));
$Model
->expects($this->any())
->method('getDataSource')
->will($this->returnValue($DataSource));
$Model->save(/* ... */);
In case you are wondering about the array(array()) for the datasource mock, this is required as the RestSource constructor doesn't supply a default value for the first argument (unlike the parent constructor).
I'm very new to testing controllers and I'm running into a problem with a method(). I believe I'm either missing something in my test or my Controller / Repository is designed incorrectly.
The application I'm writing is basically one of those secure "one time" tools. Where you create a note, the system provides you with a URL, once that url is retrieved the note is deleted. I actually have the application written but I am going back to write tests for practice (I know that's backwards).
My Controller:
use OneTimeNote\Repositories\NoteRepositoryInterface as Note;
class NoteController extends \Controller {
protected $note;
public function __construct(Note $note)
{
$this->note = $note;
}
public function getNote($url_id, $key)
{
$note = $this->note->find($url_id, $key);
if (!$note) {
return \Response::json(array('message' => 'Note not found'), 404);
}
$this->note->delete($note->id);
return \Response::json($note);
}
...
I've injected my Note interface in to my controller and all is well.
My Test
use \Mockery as M;
class OneTimeNoteTest extends TestCase {
public function setUp()
{
parent::setUp();
$this->mock = $this->mock('OneTimeNote\Repositories\EloquentNoteRepository');
}
public function mock($class)
{
$mock = M::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
public function testShouldReturnNoteObj()
{
// Should Return Note
$this->mock->shouldReceive('find')->once()->andReturn('test');
$note = $this->call('GET', '/note/1234567890abcdefg/1234567890abcdefg');
$this->assertEquals('test', $note->getContent());
}
}
...
The error I'm getting
1) OneTimeNoteTest::testShouldReturnNoteObj
ErrorException: Trying to get property of non-object
/Users/andrew/laravel/app/OneTimeNote/Controllers/NoteController.php:24
Line 24 is in reference to this line found in my controller:
$this->note->delete($note->id);
Basically my abstracted repository method delete() obviously can't find $note->id because it really doesn't exist in the testing environment. Should I create a Note within the test and try to actually deleting it? Or would that be something that should be a model test? As you can see I need help, thanks!
----- Update -----
I tried to stub the repository to return a Note object as Dave Marshall mentioned in his answer, however I'm now receiving another error.
1) OneTimeNoteTest::testShouldReturnNoteObj
BadMethodCallException: Method Mockery_0_OneTimeNote_Repositories_EloquentNoteRepository::delete() does not exist on this mock object
I do have a delete() method in my repository and I know it's working when I test my route in the browser.
public function delete($id)
{
Note::find($id)->delete();
}
You are stubbing the note repository to return a string, PHP is then trying to retrieve the id attribute of a string, hence the error.
You should stub the repository to return a Note object, something like:
$this->mock->shouldReceive('find')->once()->andReturn(new Note());
Building upon Dave's answer, I was able to figure out what my problem is. I wasn't mocking the delete() method. I didn't understand the need to mock each individual method in my controller that would be called.
I just added this line:
$mock->shouldReceive('delete')->once()->andReturnNull();
Since my delete method is just deleting the note after it is found, I went ahead and mocked it but set it to return null.
I am trying to test a Controller
that has a Command object with data binding.
The Command Object has a Service injected into it.
But When I try test the command object the injected service method
is never found as it is never "injected"
Is there a way to mock a service inside a command object?
Test method
void testLoginPasswordInvalid() {
mockRequest.method = 'POST'
mockDomain(User, [new User(login:"freddy", password:"realpassword")])
mockLogging(UserService) // userService mocked
MockUtils.prepareForConstraintsTests(LoginCommand)
def userService = new UserService()
def user = userService.getUser("freddy")//Gets called and returns the mockDomain
assert userService.getUser("freddy")//Passes
def cmd = new LoginCommand(login:"freddy", password:"letmein")
cmd.validate() // Fails (userService is nevr injected)
controller.login(cmd)
assertTrue cmd.hasErrors()
assertEquals "user.password.invalid", cmd.errors.password
assertEquals "/store/index", renderArgs.view
}
The getUser() method of the userService isn't found
Cannot invoke method getUser() on null object
java.lang.NullPointerException: Cannot invoke method getUser() on null object
Code
The login method of the controller being called,
def login = { LoginCommand cmd ->
if(request.method == 'POST') {
if(!cmd.hasErrors()){
session.user = cmd.getUser()
redirect(controller:'store')
}
else{
render(view:'/store/index', model:[loginCmd:cmd])
}
}else{
render(view:'/store/index')
}
}
The Command Object has a "userService" injected into it.
The validator calls this userService to find a user
class LoginCommand {
def userService
String login
String password
static constraints = {
login blank:false, validator:{ val, cmd ->
if(!cmd.userService.getUser()){
return "user.not.found"
}
}
}
The userService.getUser() looks like this.
class UserService {
boolean transactional = true
User getUser(String login) {
return User.findByLogin(login)
}
}
Service injection is done using Spring autowire-by-name. (Grep the Grails source tree for autowire to find a nice code fragment you can use to get it to autowire your controllers for you in integration tests.) This only functions in integration tests, where there's a Spring application context around that has the beans that can be injected.
In unit tests, you have to do this yourself since there's no Spring-land surrounding your stuff. This can be a pain, but gives you some benefits:
1) It's easy to inject mock versions of services - for example, using an Expando - in order to more closely specify the behavior of your controller's collaborating services, and to allow you to test only the controller logic rather than the controller and service together. (You can certainly do the latter in a unit test as well, but you have the choice of how to wire it up.)
2) It forces you to be explicit about the dependencies of your controller - if you depend on it, your tests will show it. This makes them a better specification for the behavior of your controller.
3) You can mock only the pieces of external collaborators your controller depends on. This helps your tests be less fragile - less likely to need to change when things change.
Short answer: your test method needs a cmd.userService = userService line.
What John says is on the mark. One example might be:
def mockUsers = [new User(login:"freddy", password:"realpassword")]
mockDomain(User, mockUsers)
def userService = [getUser:{String login -> mockUsers[0]}] as UserService
def cmd = new LoginCommand (/*arguments*/)
cmd.userService = userService
You can lookup other ways to mock objects at http://groovy.codehaus.org/Groovy+Mocks