Unit Testing a Login in ASP.NET – Stack Overflow

Introduction

JUnit is an open-source unit testing framework for Java that is used to write and run repeatable automated tests. Unit testing is one of the best test methods for regression testing.

Mockito is an open-source testing framework for Java that allows the creation of test double objects in automated unit tests for the purpose of test-driven development or behavior-driven development.

Authcontrollertest.java

@SpringBootTest annotation can be specified on a test class that runs Spring Boot based tests. It provides the following features over and above the regular Spring TestContext Framework:

  • Uses SpringBootContextLoader as the default ContextLoader when no specific @ContextConfiguration(loader=...) is defined.
  • Automatically searches for a @SpringBootConfiguration when nested @Configuration is not used, and no explicit classes are specified.
  • Allows custom Environment properties to be defined using the properties attribute.
  • Allows application arguments to be defined using the args attribute.
  • Provides support for different webEnvironment modes, including the ability to start a fully running web server listening on a defined or random port.
  • Registers a TestRestTemplate and/or WebTestClient bean for use in web tests that are using a fully running web server.

@AutoConfigureMockMvc annotation can be applied to a test class to enable and configure auto-configuration of MockMvc which provides the server-side Spring MVC test support.

Note: You can also use @WebMvcTest annotation that focuses on Spring MVC components. This annotation will disable full auto-configuration and only apply configuration relevant to MVC tests. If you are looking to load your full application configuration and use MockMVC, you should consider @SpringBootTest combining with @AutoConfigureMockMvc rather than this annotation.

Похожее:  Росгосстрах накопительная пенсия

@MockBean annotation can be used to add mocks to a Spring ApplicationContext. It can be used as a class-level annotation or on fields in either @Configuration classes or test classes that are @RunWith the SpringRunner.

package com.javachinna.controller;

import static org.mockito.ArgumentMatchers.any;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.test.web.servlet.MockMvc;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.javachinna.config.MockUserUtils;
import com.javachinna.config.WithMockCustomUser;
import com.javachinna.dto.LocalUser;
import com.javachinna.dto.LoginRequest;
import com.javachinna.dto.SignUpRequest;
import com.javachinna.dto.SocialProvider;
import com.javachinna.exception.UserAlreadyExistAuthenticationException;
import com.javachinna.model.User;
import com.javachinna.service.UserService;

import dev.samstevens.totp.code.CodeVerifier;;

@SpringBootTest
@AutoConfigureMockMvc
class AuthControllerTest {

	@Autowired
	private MockMvc mockMvc;

	@MockBean
	private UserService userService;

	@MockBean
	private CodeVerifier verifier;

	@MockBean
	private AuthenticationManager authenticationManager;

	private static User user = MockUserUtils.getMockUser("JavaChinna");

	private static ObjectMapper mapper = new ObjectMapper();

	@Test
	public void testAuthenticateUser() throws Exception {
		LocalUser localUser = LocalUser.create(user, null, null, null);
		LoginRequest loginRequest = new LoginRequest(user.getEmail(), user.getPassword());
		UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(localUser, null);
		Mockito.when(authenticationManager.authenticate(Mockito.any(UsernamePasswordAuthenticationToken.class))).thenReturn(authentication);
		String json = mapper.writeValueAsString(loginRequest);
		mockMvc.perform(post("/api/auth/signin").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))
				.andExpect(status().isOk()).andExpect(jsonPath("$.authenticated").value("true")).andExpect(jsonPath("$.accessToken").isNotEmpty());

		// Test when user 2fa is enabled
		user.setUsing2FA(true);
		mockMvc.perform(post("/api/auth/signin").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))
				.andExpect(status().isOk()).andExpect(jsonPath("$.authenticated").value("false")).andExpect(jsonPath("$.user").doesNotExist());
	}

	@Test
	public void testRegisterUser() throws Exception {
		SignUpRequest signUpRequest = new SignUpRequest("1234", "JavaChinna", user.getEmail(), user.getPassword(), user.getPassword(), SocialProvider.FACEBOOK);
		Mockito.when(userService.registerNewUser(any(SignUpRequest.class))).thenReturn(user);
		String json = mapper.writeValueAsString(signUpRequest);
		mockMvc.perform(post("/api/auth/signup").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))
				.andExpect(status().isOk()).andExpect(jsonPath("$.success").value("true")).andExpect(jsonPath("$.message").value("User registered successfully"));

		// Test when user provided email already exists in the database
		Mockito.when(userService.registerNewUser(any(SignUpRequest.class))).thenThrow(new UserAlreadyExistAuthenticationException("exists"));
		json = mapper.writeValueAsString(signUpRequest);
		mockMvc.perform(post("/api/auth/signup").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))
				.andExpect(status().isBadRequest()).andExpect(jsonPath("$.success").value("false")).andExpect(jsonPath("$.message").value("Email Address already in use!"));
	}

	@Test
	@WithMockCustomUser
	public void testVerifyCodeWhenCodeIsNotValid() throws Exception {
		Mockito.when(verifier.isValidCode(Mockito.anyString(), Mockito.anyString())).thenReturn(false);
		String json = mapper.writeValueAsString("443322");
		mockMvc.perform(post("/api/auth/verify").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))
				.andExpect(status().isBadRequest()).andExpect(jsonPath("$.success").value("false")).andExpect(jsonPath("$.message").value("Invalid Code!"));
	}

	@Test
	@WithMockCustomUser
	public void testVerifyCodeWhenCodeIsValid() throws Exception {
		Mockito.when(verifier.isValidCode(Mockito.anyString(), Mockito.anyString())).thenReturn(true);
		String json = mapper.writeValueAsString("443322");
		mockMvc.perform(post("/api/auth/verify").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))
				.andExpect(status().isOk()).andExpect(jsonPath("$.authenticated").value("true")).andExpect(jsonPath("$.accessToken").isNotEmpty())
				.andExpect(jsonPath("$.user").exists());
	}
}

How can i use a unit test to test my api’s authentication and authorization?

A colleague wrote an HTTP API. He implemented the security using a DelegatingHandler that implements basic HTTP authorization.

He added a route config to apply the BasicAuthHandler to the API route in a global config:

config.Routes.MapHttpRoute(
    name: "Api",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional },
    constraints: null,
    handler: BasicAuthHandler
);

I wrote a unit test to test the API call:

[TestClass]
public class ApiControllerTest
{
  private ApiRepository repo = new ApiTestRepository();

  [TestMethod]
  public void Get()
  {
    // Arrange
    var config = new HttpConfiguration();
    var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/api/driver/1");
    var route = config.Routes.MapHttpRoute("Default", "api/{controller}/{id}");

    ApiDriverController controller = new ApiDriverController(repo)
    {
      Request = request,
    };

    controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;

    // Act
    var Results = controller.Get(1);

    // Assert
    // ...
  }
}

When I use a browser to call the API, it does have security. However, the API test doesn’t seem to require it.

Is there a reason the API test works when it shouldn’t? Is there a way I can test the security?

Login api unit test cases

This test method is responsible for unit testing the SignIn API. It covers the following 2 scenarios.

Setup authentication

Create new Startup.Auth.cs file which will contain the setup of authentication:

The Startup.Auth.cs contains the partials Startup class and initialises identity environment:

Setup project

The following article will add ASP.NET Core Identity to the sample project used by: Implementing SOLID REST API using ASP.NET Core.

In order to use OpenIddict, add the appropriate MyGet repositories to your NuGet sources. This can be done by adding a new NuGet.Config file at the root of your solution:

In order to use ASP.NET Core Identity and OpenIddict add the following packages to your project:

Unit testing authentication controller

Since application security is one of the critical aspects of an application, it’s our responsibility to unit test to make sure that it is defect-free and working as expected. Hence, we are gonna write some unit test cases for our AuthConroller in this Spring Boot Angular MySQL Maven Application.

Unit-тесты на c# — разработка на

А вы когда-нибудь задумывались о необходимости тестирования разрабатываемых приложений? Сегодня я попробую показать важность применения unit-тестов, которые призваны помочь в обнаружении ошибок на ранних этапах работы, что в последующем приводит к экономии ваших средств и ресурсов.

В процессе написания ПО у меня возникло понимание о целесообразности применения unit-тестов.

В моей практике появилось несколько проектов, в которых мне довелось писать unit-тесты, каждый из которых выполнял определенную роль — поиск ошибок в основных алгоритмах кода, нагрузочное тестирование и отладка бэкенда веб-приложения.

В каждой из поставленных задач unit-тесты оказались эффективны, позволив существенно сократить время работы и обеспечить своевременное обнаружение ошибок кода.

Согласно данным[1] исследований, цена ошибки в ходе разработки и поддержании ПО экспоненциально возрастает при несвоевременном их обнаружении.

На представленном рисунке видно, что при выявлении ошибки на этапе формирования требований мы получим экономию средств в соотношении 200:1 по сравнению с их обнаружением на этапе поддержки.

Среди всех тестов львиную долю занимают именно unit-тесты. В классическом понимании unit-тесты позволяют быстро и автоматически протестировать отдельные части ПО независимо от остальных.

Рассмотрим простой пример создания unit-тестов. Для этого создадим консольное приложение Calc, которое умеет делить и суммировать числа.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Calc
{
class Program
{
static void Main(string[] args)
{
}
}
}

Добавляем класс, в котором будут производиться математические операции.

using System;

namespace Calc
{
/// <summary>
/// Выполнение простых математических действий над числами
/// </summary>
public class Calculator
{
/// <summary>
/// Получаем результат операции деления (n1 / n2)
/// </summary>
/// <param name=”n1″>Первое число</param>
/// <param name=”n2″>Второе число</param>
/// <returns>Результат</returns>
public double Div(double n1, double n2)
{
// Проверка деления на “0”
if (n2 == 0.0D)
throw new DivideByZeroException();
return n1 / n2;
}

/// <summary>
/// Получаем результат сложения чисел и их увеличения на единицу
/// </summary>
/// <param name=”n1″></param>
/// <param name=”n2″></param>
/// <returns></returns>
public double AddWithInc(double n1, double n2)
{
return n1 n2 1;
}
}
}

Так, в методе Div производится операция деления числа n1 на число n2. Если передаваемое число n2 будет равняться нулю, то такая ситуация приведет к исключению. Для этого знаменатель этой операции проверяется на равенство нулю.

Метод AddWithInc производит сложение двух передаваемых чисел и инкрементацию полученного результата суммирования на единицу.

На следующем шаге добавим в решение проект тестов.

Пустой проект unit-тестов:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CalcTests
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
}
}
}

Переименуем наш проект: «SimpleCalculatorTests». Добавляем ссылку на проект Calc.

В проекте Calc содержатся 2 метода, которые надо протестировать на корректность работы. Для этого создадим 3 теста, которые будут проверять операцию деления двух чисел, операцию деления на нуль и операцию сложения двух чисел и инкрементацию полученной суммы.

Добавляем в проект тест для проверки метода AddWithInc.

/// <summary>
/// Тест проверки метода AddWithInc
/// </summary>
[TestMethod]
public void AddWithInc_2Plus3Inc1_Returned6()
{
// arrange
var calc = new Calculator();
double arg1 = 2;
double arg2 = 3;
double expected = 6;
// act
double result = calc.AddWithInc(arg1, arg2);
// assert
Assert.AreEqual(expected, result);
}

В тесте создаются 3 переменные — это аргументы, передаваемые в метод AddWithInc, и ожидаемый результат, возвращаемый этим методом. Результат выполнения метода будет записан в переменную result.

На следующем шаге происходит сравнение ожидаемого результата с реальным числом метода AddWithInc. При совпадении результата с ожидаемым числом, то есть числом 6, тест будет считаться положительным и пройденным. Если полученный результат будет отличаться от числа 6, то тест считается проваленным.

Следующим тестом мы будем проверять метод Div

[TestMethod]
public void Div_4Div2_Returned2()
{
// arrange
var calc = new Calculator();
double arg1 = 4;
double arg2 = 2;
double expected = 2;
// act
double result = calc.Div(arg1, arg2);
// assert
Assert.AreEqual(expected, result);
}

Аналогичным образом создаются два аргумента и ожидаемый результат выполнения метода Div. Если результат деления 4/2 в методе равен 2, то тест считается пройдённым. В противном случае — не пройденным.

Следующий тест будет проверять операцию деления на нуль в методе Div.

[TestMethod]
[ExpectedException(typeof(DivideByZeroException),
“Oh my god, we can’t divison on zero”)]
public void Div_4Div0_ZeroDivException()
{
// arrange
var calc = new Calculator();
double arg1 = 4;
double arg2 = 0;
// act
double result = calc.Div(arg1, arg2);
// assert
}

Тест будет считаться пройденным в случае возникновения исключения DivideByZeroException — деление на нуль. В отличии от двух предыдущих тестов, в этом тесте нет оператора Assert. Здесь обработка ожидаемого результата производится с помощью атрибута «ExpectedException».

Если аргумент 2 равен нулю, то в методе Divвозникнет исключение — деление на нуль. В таком случае тест считается пройденным. В случае, когда аргумент 2 будет отличен от нуля, тест считается проваленным.

Для запуска теста необходимо открыть окно Test Explorer. Для этого нажмите Test -> Windows -> Test Explorer (Ctrl , T). В появившемся окне можно увидеть 3 добавленных теста:

Для запуска всех тестов нажмите Test -> Run -> All tests (Ctrl , A).

Если тесты выполнятся успешно, в окне Test Explorer отобразятся зеленые пиктограммы, обозначающие успешность выполнения.

В противном случае пиктограммы будут красными.

Unit-тесты имеют обширную, строго не регламентированную область применения — зачастую фантазия самого автора кода подсказывает решение нестандартных задач с помощью этого инструмента.

Случай написания тестов для бэкенда веб-приложения в моей практике является не совсем стандартным вариантом применения unit-тестов. В данной ситуации unit-тесты вызывали методы контроллера MVC-приложения, в то же время передавая тестовые данные в контроллеры.

Далее в режиме отладки шаг за шагом выполнялись все действия алгоритма. В этом случае применение тестов позволило произвести быструю отладку бэкенда веб-приложения.

Существуют случаи, когда модульные тесты применять нецелесообразно. Например, если вы веб-разработчик, который делает сайты, где мало логики. В таких случаях имеются только представления, как, например, для сайтов-визиток, рекламных сайтов, или, когда вам поставлена задача реализовать пилотный проект «на посмотреть, что получится». У вас ограниченные ресурсы и время. А ПО будет работать только один день — для показа руководству.

Сжатые сроки, малый бюджет, размытые цели или довольно несложные требования — случаи, в которых вы не получите пользы от написания тестов.

Для определения целесообразности использования unit-тестов можно воспользоваться следующим методом: возьмите лист бумаги и ручку и проведите оси X и Y. X — алгоритмическая сложность, а Y — количество зависимостей. Ваш код поделим на 4 группы.

  1. Простой код (без каких-либо зависимостей)
  2. Сложный код (содержащий много зависимостей)
  3. Сложный код (без каких-либо зависимостей)
  4. Не очень сложный код (но с зависимостями)

Первое — это случай, когда все просто и тестировать здесь ничего не нужно.

Второе — случай, когда код состоит только из плотно переплетенных в один клубок реализаций, перекрестно вызывающих друг друга. Тут неплохо было бы провести рефакторинг. Именно поэтому тесты писать в этом случае не стоит, так как код все равно будет переписан.

Третье — случай алгоритмов, бизнес-логики и т.п. Важный код, поэтому его нужно покрыть тестами.

Четвертый случай — код объединяет различные компоненты системы. Не менее важный случай.

Последние два случая — это ответственная логика. Особенно важно писать тесты для ПО, которые влияют на жизни людей, экономическую безопасность, государственную безопасность и т.п.

Подводя итог всего описанного выше хочется отметить, что тестирование делает код стабильным и предсказуемым. Поэтому код, покрытый тестами, гораздо проще масштабировать и поддерживать, т.к. появляется большая доля уверенности, что в случае добавления нового функционала нигде ничего не сломается. И что не менее важно — такой код легче рефакторить.

[1] Данные взяты из книги «Технология разработки программного обеспечения» автора Ларисы Геннадьевны Гагариной

Conclusion

That’s all folks. In this article, we have implemented unit test cases for our REST controller using Junit 5 and Mockito.

If you like learning from videos for testing/writing JUnit tests, then make sure to check out Philip’s Testing Spring Boot Applications Masterclass (if you buy through this link, I get a cut).

Thank you for reading.

Read Next:Test REST Controller with Spring Security using Mock Authentication or Disable Security in JUnit Tests

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *