Symfony 4.1: How to use Dependency Injection in UnitTest (Swift_Mailer) - unit-testing

In my Symfony4.1-project i am trying to test a method, which should send mails using SwiftMailer, via a unit test.
My test class looks like this
namespace App\Tests;
use App\Controller\UserImageValidationController;
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
class UserImageValidationControllerTest extends TestCase
{
private $mailer = null;
public function __construct(\Swift_Mailer $testmySwiftMailer)
{
$this->mailer = $testmySwiftMailer;
}
public function testMail()
{
$controller = new UserImageValidationController();
$controller->notifyOfMissingImage(
'a',
'b',
'c',
'd',
$this->mailer
);
}
}
The problem is, that when i run ./bin/phpunit i get an exception saying
Uncaught ArgumentCountError: Too few arguments to function App\Tests\UserImageValidationControllerTest::__construct(), 0 [...] and exactly 1 expected [...]
It seemes like in the test environment DI isn't working.
So i added
bind:
$testmySwiftMailer: '#swiftmailer.mailer.default'
To my config/services_test.yaml but i still got the same error.
I also added autowiring: true to that file (just to try it) and it also doesn't work.
Also, i tried with a service-alias, like it states in the file's comments: still no success.
How do i get the swiftmailer injected into my test case constructor?

Tests are not part of the container and don't act as services so your solution is not valid. Extend Symfony\Bundle\FrameworkBundle\Test\KernelTestCase and do that instead (making sure your service is public first):
protected function setUp()
{
static::bootKernel();
$this->mailer = static::$kernel->getContainer()->get('mailer');
}
protected function tearDown()
{
$this->mailer = null;
}

The accepted answer will not work for services that are not defined as public. However after Symfony 4.1, to be able to access private services while on testing, you need to get the service from a special testing container.
From the Symfony documentation:
tests based on WebTestCase and KernelTestCase now access to a special container via the static::$container property that allows fetching non-removed private services
example:
namespace App\Tests;
use App\Controller\UserImageValidationController;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class UserImageValidationControllerTest extends WebTestCase
{
private $mailer = null;
protected function setUp()
{
self::bootKernel();
// gets the special container that allows fetching private services
$container = self::$container;
$this->mailer = $container->get('mailer');
}
public function testMail()
{
$controller = new UserImageValidationController();
$controller->notifyOfMissingImage(
'a',
'b',
'c',
'd',
$this->mailer
);
}
}

Related

How to unit test a service call in xUnit and nSubstitute

I been trying to figure out how i can unit test service and so far have got nowhere.
I am using xUnit and NSubstitute (as advised by friends), below is the simple test that i want to run (which fails currently).
public class UnitTest1
{
private readonly RallyService _rallyService;
public UnitTest1(RallyService rallyService)
{
_rallyService= rallyService;
}
[Fact]
public void Test1()
{
var result = _rallyService.GetAllRallies();
Assert.Equal(2, result.Count());
}
}
My rally service class makes a simple call to the db to get all Rally entites and returns those:
public class RallyService : IRallyService
{
private readonly RallyDbContext _context;
public RallyService(RallyDbContext context)
{
_context = context;
}
public IEnumerable<Rally> GetAllRallies()
{
return _context.Rallies;
}
}
Any guidance would be appreciated.
Since you use .NET Core, I assume you also use Entity Framework Core. While it was possible to mock most of the operations in the previous EF version, however the EF Core suggests to use in-memory database for unit testing. I.e. you don't need to mock RallyDbContext, hence NSubstitute is not needed for this particular test. You would need NSubstitute to mock the service when testing a controller or application using the service.
Below is your Test1 written using in-memory database.
public class UnitTest1
{
private readonly DbContextOptions<RallyDbContext> _options;
public UnitTest1()
{
// Use GUID for in-memory DB names to prevent any possible name conflicts
_options = new DbContextOptionsBuilder<RallyDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
}
[Fact]
public async Task Test1()
{
using (var context = new RallyDbContext(_options))
{
//Given 2 records in database
await context.AddRangeAsync(new Rally { Name = "rally1" }, new Rally { Name = "rally2" });
await context.SaveChangesAsync();
}
using (var context = new RallyDbContext(_options))
{
//When retrieve all rally records from the database
var service = new RallyService(context);
var rallies = service.GetAllRallies();
//Then records count should be 2
Assert.Equal(2, rallies.Count());
}
}
}
A working test application with this unit test is in my GitHub for your reference. I used SQL Express in the actual app.
I don't think it is standard to have a unit test constructor with a parameter. The unit test runner will new up this class, and unless you are using something that will auto-inject that parameter I think the test will fail to run.
Here is a standard fixture layout:
public class SampleFixture {
[Fact]
public void SampleShouldWork() {
// Arrange stuff we need for the test. This may involved configuring
// some dependencies, and also creating the subject we are testing.
var realOrSubstitutedDependency = new FakeDependency();
realOrSubstitutedDependency.WorkingItemCount = 42;
var subject = new Subject(realOrSubstitutedDependency);
// Act: perform the operation we are testing
var result = subject.DoWork();
// Assert: check the subject's operation worked as expected
Assert.Equal(42, result);
}
[Fact]
public void AnotherTest() { /* ... */ }
}
If you need a common setup between tests, you can use a parameterless constructor and do common initialisation there.
In terms of the specific class you are trying to test, you need to make sure your RallyDbContext is in a known state to repeatably and reliably test. You may want to look up answers specific to testing Entity Framework for more information.

MVC Core 2.0 Unit Testing and Automapper

I am attempting to Unit Test a method that uses Automapper ProjectTo and I'm not sure how to register the mappings in MVC Core. I am using the built in unit testing.
The following is my unit test.
[TestClass]
public class BusinessGenderServiceTest
{
[ClassInitialize]
public static void Init(TestContext context)
{
}
[TestMethod]
public void GetTest()
{
var options = new DbContextOptionsBuilder<GotNextDbContext>()
.UseInMemoryDatabase(databaseName: "GetTest")
.Options;
using (var context = new GotNextDbContext(options))
{
context.GenderLanguage.Add(new GenderLanguage { Id = 1, Name = "Male", Language = 1 });
context.GenderLanguage.Add(new GenderLanguage { Id = 2, Name = "Female", Language = 1 });
context.GenderLanguage.Add(new GenderLanguage { Id = 3, Name = "Hombre", Language = 2 });
context.GenderLanguage.Add(new GenderLanguage { Id = 4, Name = "Hombre", Language = 2 });
context.SaveChanges();
}
using (var context = new GotNextDbContext(options))
{
var service = new GenderService(context);
var result = service.Get(1);
Assert.AreEqual(2, result.Count());
}
}
}
I am getting the following error when I run the test:
Message: Test method GotNext.Test.BusinessGenderServiceTest.GetTest threw exception:
System.InvalidOperationException: Mapper not initialized. Call Initialize with appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance.
I was able to solve this problem by configuring and initializing automapper in the Init method of each test class.
For example
[ClassInitialize]
public static void Init(TestContext testContext)
{
var mappings = new MapperConfigurationExpression();
mappings.AddProfile<LocationProfile>();
mappings.AddProfile<CompanyProfile>();
Mapper.Initialize(mappings);
}
You can configure AutoMapper in class like this:
public static class AutoMapperConfig
{
public static IMapper Initialize()
{
return new MapperConfiguration((cfg =>
{
cfg.CreateMap<User, UserDto>();
})).CreateMapper();
}
}
And next use it in startup.cs ConfigureService method
services.AddSingleton(AutoMapperConfig.Initialize());
Create a class or classes that configure AutoMapper and instantiate (and call methods, if applicable) in the Startup class.
I got this same error ("System.InvalidOperationException: Mapper not initialized. Call Initialize with appropriate configuration. ...") when I inadvertently / mindlessly switched between AutoMapper's Instance API (which I did have configured) and AutoMapper's Static API (which I did NOT have configured).
Looking closely at the line of code flagged in the error message, I realized I used upper-case 'M' Mapper.Map() instead of my instance member lower-case 'm' mapper.Map().

Non virtual methods can not be intercepted

I am new to FakeItEasy and try solve a problem.
I have a class
public class Events
{
public List<Events> SaveEvents()
{
// Call to repository and return 1(success) or -1(fail)
//If the repository return 1 then need to make another call to save the action in db
//Sample Code here
AuditLogService log = new AuditLogService();
log.CallLog();
}
}
Here is the Test Code:
[TestMethod]
public void EventValidation()
{
//Arrange
var Fakeevents = A.Fake<Events>();
var log = A.Fake<AuditLogService>();
var _EventsController = new EventsController(Fakeevents);
_EventsController.SaveEvents();
A.CallTo(
() => Fakeevents.SaveEvents().Retunr(1).AssignsOutAndRefParameters(status)
A.CallTo(
() => log.CallLog()).MustHaveHappened(Repeated.AtLeast.Once);
}
I am getting error like "Non virtual methods can not be intercepted"
I want to check whether the Calllog method is called after success or not.
Can anyone please help me on this.
I have a method and inside a method i am initiating another class and calling a method of the class. I want to check from fakeItEasy whether the method is called.
Unfortunately, your title says it all. Non-virtual members cannot be faked, configured, or intercepted, as noted in the documentation under "What members can be overridden?".
There's nothing that FakeItEasy can do for you unless you make the member virtual (or promote it to an interface and fake the interface, or something similar).
Have you tried to use function?
Like this:
Func<YourReturnType> action = () => YourMethod(params); // Act
action.Should().Throw<Exception>(); // Assert
var log = A.Fake();
Use interface instead of AuditLogService. And have this class implement that interface
var log = A.Fake();

Mocking 2 data repositories at the same time with Laravel 4

I am developing an application using Laravel 4 and trying to follow TDD. I use, following tutorials from Jeffrey Way or Philip Brown , repositories for my database. I had problems with that before ( Mockery not calling method from repository (interface) ) but everything is working fine now in my tests. However, I do get an error trying to mock 2 repositories in the same test, like this:
class PedidosControllerTest extends TestCase {
private $mock;
private $pedidoModelMock;
private $mockCliente;
private $clienteModelMock;
function setUp() {
parent::setUp();
$this->mock = $this->mock('repositories\canarias\PedidoDbRepository');
$this->pedidoModelMock = Mockery::mock('Pedido');
$this->mockCliente = $this->mock('repositories\canarias\ClienteDbRepository');
$this->clienteModelMock = Mockery::mock('Cliente');
}
public function mock($class)
{
$mock = Mockery::mock('Model', $class);
$this->app->instance($class, $mock);
return $mock;
}
protected function tearDown()
{
Mockery::close();
}
public function testIndexWithClient()
{
$nestedView = 'pedidos.index';
$this->registerNestedView($nestedView);
$this->mockCliente
->shouldReceive('find')
->once()
->with(698)
->andReturn($this->clienteModelMock);
$this->mock
->shouldReceive('findAllFromCliente')
->once()
->with(698)
->andReturn($this->pedidoModelMock);
$this->clienteModelMock
->shouldReceive('getAttribute')
->once()
->with('nombre')
->andReturn('Pepito');
$this->call('GET', '/clientes/698/pedidos');
$this->assertResponseOk();
$this->assertViewHas('pageAttributes');
$this->assertViewHas('contenido');
$this->assertNestedViewHas($nestedView, 'pedidos');
$this->assertNestedViewHas($nestedView, 'cliente');
}
}
From what I've tested (no pun intended), the problem seems to be related with this code shared by both $this->mock and $this->mockCliente:
Mockery::mock('Model', $class);
I get an error saying that Model class doesn't exist. In other functions of the test, where I just use ONE mock, that class is found indeed, so it's not related with the name being misspelled or something like that.
Is that Model class somehow "lost" the first time is mocked?
It turned out it was indeed a problem with the class being mispelled (idiot mistake, I know). It should be
$this->mockCliente = $this->mock('repositories\canarias\ClientesDbRepository');
instead of
$this->mockCliente = $this->mock('repositories\canarias\ClienteDbRepository');

How to pass JSON to route from unit test?

I am unit testing my Laravel 4 Controller by mocking my repository that the controller expects. The problem is with the "store" function. This is the function that is called by Laravel when I do a POST to the given controller. The function gets called, but it is expected itemData as an input but I don't know how to provide that. Here is what I've tried:
ItemEntryController
class ItemEntryController extends BaseController
{
protected $itemRepo;
public function __construct(ItemEntryRepositoryInterface $itemRepo)
{
$this->itemRepo = $itemRepo;
}
public function store()
{
if(Input::has('itemData'))
{
$data = Input::get('itemData');
return $this->itemRepo->createAndSave($data);
}
}
}
Test class
<?php
use \Mockery as m;
class ItemEntryRouteAndControllerTest extends TestCase {
protected $testItemToStore = '{"test":12345}';
public function setUp()
{
parent::setUp();
$this->mock = $this->mock('Storage\ItemEntry\ItemEntryRepositoryInterface');
}
public function mock($class)
{
$mock = m::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
public function testItemStore()
{
Input::replace($input = ['itemData' => $this->testItemToStore]);
$this->mock
->shouldReceive('createAndSave')
->once()
->with($input);
$this->call('POST', 'api/v1/tools/itementry/items');
}
Well, you got a few options.
Integration testing
You may want to follow the unit testing docs, which actually has a call() method which allows you set all of this. This bootstraps the app and will use your databases, etc.
This is more of an integration test than unit test, as it uses your actual class implementations.
This may actually be preferable, as Unit testing controllers may not actually make much sense (it doesn't do much, in theory, but call other already-unit-tested classes). But this gets into unit testing vs integration testing vs acceptance testing and all the nuances that apply therein. (Read up!)
Unit Testing
If you're actually looking to unit test, then you need to make your controller unit-testable (ha!). This (likely) means injecting all dependencies:
class ItemEntryController extends BaseController
{
protected $itemRepo;
// Not pictured here is actually making sure an instance of
// Request is passed to this controller (via Service Provider or
// IoC binding)
public function __construct(ItemEntryRepositoryInterface $itemRepo, Request $input)
{
$this->itemRepo = $itemRepo;
$this->request = $input;
}
public function store()
{
if($this->input->has('itemData'))
{
// Get() is actually a static method so we use
// the Request's way of getting the $_GET/$_POST variables
// see note below!
$data = $this->input->input('itemData');
return $this->itemRepo->createAndSave($data);
}
}
}
Sidenote: The Input facade is actually an instance of Request objet with an extra static method get()!
So now that we aren't using Input any longer, and are injecting the Request object, we can unit test this class by mocking the Request object.
Hope that helps!